Merge branch 'next' into core/magic-entry

This commit is contained in:
Norbert de Langen 2019-11-14 12:27:00 +01:00
commit 54cddeb789
520 changed files with 6704 additions and 3765 deletions

View File

@ -13,7 +13,10 @@ const withTests = {
};
module.exports = {
ignore: ['./lib/codemod/src/transforms/__testfixtures__'],
ignore: [
'./lib/codemod/src/transforms/__testfixtures__',
'./lib/postinstall/src/__testfixtures__',
],
presets: [
['@babel/preset-env', { shippedProposals: true, useBuiltIns: 'usage', corejs: '3' }],
'@babel/preset-typescript',

View File

@ -1,3 +1,22 @@
## 5.3.0-alpha.45 (November 14, 2019)
### Breaking Changes
* CSF: Deprecate displayName parameter ([#8775](https://github.com/storybookjs/storybook/pull/8775))
### Features
* Addon-docs: Rich prop tables ([#8826](https://github.com/storybookjs/storybook/pull/8826))
* Core: Simplified hierarchy separators ([#8796](https://github.com/storybookjs/storybook/pull/8796))
* CLI: Upgrade hierarchy separator codemod + examples ([#8818](https://github.com/storybookjs/storybook/pull/8818))
* CLI: Addon postinstall hooks ([#8700](https://github.com/storybookjs/storybook/pull/8700))
* CSF/MDX: Add component id for permalinks ([#8808](https://github.com/storybookjs/storybook/pull/8808))
* Addon-knobs: Add object[] support for select ([#7957](https://github.com/storybookjs/storybook/pull/7957))
### Bug Fixes
* Addon-A11y: Show errors, reset config properly ([#8779](https://github.com/storybookjs/storybook/pull/8779))
## 5.3.0-alpha.44 (November 13, 2019)
### Features

View File

@ -327,9 +327,7 @@ First we are going to install storybook, then we are going to link `@storybook/r
You should now have a working storybook dev environment up and running.
> TODO: update this section (is already incorrect)
Save and go to `http://localhost:9009` (or wherever storybook is running)
Save and go to `http://localhost:9011` (or wherever storybook is running)
If you don't see the changes rerun `yarn storybook` again in your sandbox app

View File

@ -5,7 +5,9 @@
- [Create React App preset](#create-react-app-preset)
- [Description doc block](#description-doc-block)
- [React Native Async Storage](#react-native-async-storage)
- [Deprecate displayName parameter](#deprecate-displayname-parameter)
- [Unified docs preset](#unified-docs-preset)
- [Simplified hierarchy separators](#simplified-heirarchy-separators)
- [From version 5.1.x to 5.2.x](#from-version-51x-to-52x)
- [Source-loader](#source-loader)
- [Default viewports](#default-viewports)
@ -109,10 +111,30 @@ getStorybookUI({
});
```
### Deprecate displayName parameter
In 5.2, the story parameter `displayName` was introduced as a publicly visible (but internal) API. Storybook's Component Story Format (CSF) loader used it to modify a story's display name independent of the story's `name`/`id` (which were coupled).
In 5.3, the CSF loader decouples the story's `name`/`id`, which means that `displayName` is no longer necessary. Unfortunately, this is a breaking change for any code that uses the story `name` field. Storyshots relies on story `name`, and the appropriate migration is to simply update your snapshots. Apologies for the inconvenience!
### Unified docs preset
Addon-docs configuration gets simpler in 5.3. In 5.2, each framework had its own preset, e.g. `@storybook/addon-docs/react/preset`. Starting in 5.3, everybody should use `@storybook/addon-docs/preset`.
### Simplified hierarchy separators
We've deprecated the ability to specify the hierarchy separators (how you control the grouping of story kinds in the sidebar). From Storybook 6.0 we will have a single separator `/`, which cannot be configured.
If you are currently using using custom separators, we encourage you to migrate to using `/` as the sole separator. If you are using `|` or `.` as a separator currently, (we will soon provide) a codemod that can be used to rename all your components.
If you were using `|` and wish to keep the "root" behaviour, use the `showRoots: true` option to re-enable roots:
```js
addParameters({ options: { showRoots: true } });
```
NOTE: it is no longer possible to have some stories with roots and others without. If you want to keep the old behaviour, simply add a root called "Others" to all your previously unrooted stories.
## From version 5.1.x to 5.2.x
### Source-loader

View File

@ -56,7 +56,6 @@ import { withA11y } from '@storybook/addon-a11y';
addDecorator(withA11y)
addParameters({
a11y: {
// ... axe options
element: '#root', // optional selector which element to inspect
config: {}, // axe-core configurationOptions (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#parameters-1)
options: {} // axe-core optionsParameter (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter)

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-a11y",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "a11y addon for storybook",
"keywords": [
"a11y",
@ -33,12 +33,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/client-logger": "5.3.0-alpha.44",
"@storybook/components": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/theming": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"@storybook/client-logger": "5.3.0-alpha.45",
"@storybook/components": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"@storybook/theming": "5.3.0-alpha.45",
"axe-core": "^3.3.2",
"core-js": "^3.0.1",
"global": "^4.3.2",

View File

@ -63,7 +63,7 @@ function ThemedA11YPanel(props) {
}
describe('A11YPanel', () => {
it('should register STORY_RENDERED and RESULT updater on mount', () => {
it('should register STORY_RENDERED, RESULT and ERROR updater on mount', () => {
// given
const api = createApi();
expect(api.on).not.toHaveBeenCalled();
@ -72,9 +72,10 @@ describe('A11YPanel', () => {
mount(<ThemedA11YPanel api={api} />);
// then
expect(api.on.mock.calls.length).toBe(2);
expect(api.on.mock.calls.length).toBe(3);
expect(api.on.mock.calls[0][0]).toBe(STORY_RENDERED);
expect(api.on.mock.calls[1][0]).toBe(EVENTS.RESULT);
expect(api.on.mock.calls[2][0]).toBe(EVENTS.ERROR);
});
it('should request a run on tab activation', () => {
@ -93,7 +94,7 @@ describe('A11YPanel', () => {
expect(wrapper.find(ScrollArea).length).toBe(0);
});
it('should deregister STORY_RENDERED and RESULT updater on unmount', () => {
it('should deregister STORY_RENDERED, RESULT and ERROR updater on unmount', () => {
// given
const api = createApi();
const wrapper = mount(<ThemedA11YPanel api={api} />);
@ -103,9 +104,10 @@ describe('A11YPanel', () => {
wrapper.unmount();
// then
expect(api.off.mock.calls.length).toBe(2);
expect(api.off.mock.calls.length).toBe(3);
expect(api.off.mock.calls[0][0]).toBe(STORY_RENDERED);
expect(api.off.mock.calls[1][0]).toBe(EVENTS.RESULT);
expect(api.off.mock.calls[2][0]).toBe(EVENTS.ERROR);
});
it('should update run result', () => {

View File

@ -46,26 +46,35 @@ const Incomplete = styled.span<{}>(({ theme }) => ({
color: theme.color.warning,
}));
const centeredStyle = {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
};
const Loader = styled(({ className }) => (
<div className={className}>
<Icon inline icon="sync" status="running" /> Please wait while the accessibility scan is running
...
</div>
))({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
});
))(centeredStyle);
Loader.displayName = 'Loader';
interface A11YPanelState {
status: string;
interface A11YPanelNormalState {
status: 'ready' | 'ran' | 'running';
passes: Result[];
violations: Result[];
incomplete: Result[];
}
interface A11YPanelErrorState {
status: 'error';
error: unknown;
}
type A11YPanelState = A11YPanelNormalState | A11YPanelErrorState;
interface A11YPanelProps {
active: boolean;
api: API;
@ -84,6 +93,7 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
api.on(STORY_RENDERED, this.request);
api.on(EVENTS.RESULT, this.onUpdate);
api.on(EVENTS.ERROR, this.onError);
}
componentDidUpdate(prevProps: A11YPanelProps) {
@ -101,6 +111,7 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
const { api } = this.props;
api.off(STORY_RENDERED, this.request);
api.off(EVENTS.RESULT, this.onUpdate);
api.off(EVENTS.ERROR, this.onError);
}
onUpdate = ({ passes, violations, incomplete }: AxeResults) => {
@ -124,6 +135,13 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
);
};
onError = (error: unknown) => {
this.setState({
status: 'error',
error,
});
};
request = () => {
const { api, active } = this.props;
@ -142,8 +160,22 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
};
render() {
const { passes, violations, incomplete, status } = this.state;
const { active } = this.props;
if (!active) return null;
// eslint-disable-next-line react/destructuring-assignment
if (this.state.status === 'error') {
const { error } = this.state;
return (
<div style={centeredStyle}>
The accessibility scan encountered an error.
<br />
{error}
</div>
);
}
const { passes, violations, incomplete, status } = this.state;
let actionTitle;
if (status === 'ready') {
@ -162,7 +194,7 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
);
}
return active ? (
return (
<Fragment>
<Provider store={store}>
{status === 'running' ? (
@ -218,6 +250,6 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
/>
</Provider>
</Fragment>
) : null;
);
}
}

View File

@ -177,6 +177,10 @@ exports[`A11YPanel should render report 1`] = `
"storybook/a11y/result",
[Function],
],
Array [
"storybook/a11y/error",
[Function],
],
],
"results": Array [
Object {
@ -187,6 +191,10 @@ exports[`A11YPanel should render report 1`] = `
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
],
},
}

View File

@ -6,5 +6,6 @@ export const ADD_ELEMENT = 'ADD_ELEMENT';
export const CLEAR_ELEMENTS = 'CLEAR_ELEMENTS';
const RESULT = `${ADDON_ID}/result`;
const REQUEST = `${ADDON_ID}/request`;
const ERROR = `${ADDON_ID}/error`;
export const EVENTS = { RESULT, REQUEST };
export const EVENTS = { RESULT, REQUEST, ERROR };

View File

@ -39,7 +39,8 @@ const run = (element: ElementContext, config: Spec, options: RunOptions) => {
restoreScroll: true,
} as RunOptions) // cast to RunOptions is necessary because axe types are not up to date
)
.then(report);
.then(report)
.catch(error => addons.getChannel().emit(EVENTS.ERROR, String(error)));
});
};
@ -47,12 +48,20 @@ if (module && module.hot && module.hot.decline) {
module.hot.decline();
}
let storedDefaultSetup: Setup | null = null;
export const withA11y = makeDecorator({
name: 'withA11Y',
parameterName: PARAM_KEY,
wrapper: (getStory, context, { parameters }) => {
if (parameters) {
setup = parameters as Setup;
if (storedDefaultSetup === null) {
storedDefaultSetup = { ...setup };
}
Object.assign(setup, parameters as Setup);
} else if (storedDefaultSetup !== null) {
Object.assign(setup, storedDefaultSetup);
storedDefaultSetup = null;
}
addons.getChannel().on(EVENTS.REQUEST, () => run(setup.element, setup.config, setup.options));

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-actions",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Action Logger addon for storybook",
"keywords": [
"storybook"
@ -28,12 +28,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/client-api": "5.3.0-alpha.44",
"@storybook/components": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/theming": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"@storybook/client-api": "5.3.0-alpha.45",
"@storybook/components": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"@storybook/theming": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"fast-deep-equal": "^2.0.1",
"global": "^4.3.2",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-backgrounds",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "A storybook addon to show different backgrounds for your preview",
"keywords": [
"addon",
@ -32,12 +32,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/client-logger": "5.3.0-alpha.44",
"@storybook/components": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/theming": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"@storybook/client-logger": "5.3.0-alpha.45",
"@storybook/components": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"@storybook/theming": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"memoizerific": "^1.11.3",
"react": "^16.8.3",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-centered",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Storybook decorator to center components",
"keywords": [
"addon",
@ -29,7 +29,7 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"global": "^4.3.2",
"util-deprecate": "^1.0.2"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-contexts",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Storybook Addon Contexts",
"keywords": [
"preact",
@ -27,10 +27,10 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/components": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"@storybook/components": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"global": "^4.3.2",
"qs": "^6.6.0"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-cssresources",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "A storybook addon to switch between css resources at runtime for your story",
"keywords": [
"addon",
@ -32,10 +32,10 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/components": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"@storybook/components": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"global": "^4.3.2",
"react": "^16.8.3"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-design-assets",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Design asset preview for storybook",
"keywords": [
"addon",
@ -34,12 +34,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/client-logger": "5.3.0-alpha.44",
"@storybook/components": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/theming": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"@storybook/client-logger": "5.3.0-alpha.45",
"@storybook/components": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"@storybook/theming": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"global": "^4.3.2",
"react": "^16.8.3",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-docs",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Superior documentation for your components",
"keywords": [
"addon",
@ -23,6 +23,7 @@
"angular/**/*",
"common/**/*",
"html/**/*",
"postinstall/**/*",
"react/**/*",
"vue/**/*",
"web-components/**/*",
@ -44,15 +45,21 @@
"@mdx-js/loader": "^1.1.0",
"@mdx-js/mdx": "^1.1.0",
"@mdx-js/react": "^1.0.27",
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/components": "5.3.0-alpha.44",
"@storybook/router": "5.3.0-alpha.44",
"@storybook/source-loader": "5.3.0-alpha.44",
"@storybook/theming": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"@storybook/components": "5.3.0-alpha.45",
"@storybook/postinstall": "5.3.0-alpha.45",
"@storybook/router": "5.3.0-alpha.45",
"@storybook/source-loader": "5.3.0-alpha.45",
"@storybook/theming": "5.3.0-alpha.45",
"acorn": "^7.1.0",
"acorn-jsx": "^5.1.0",
"acorn-walk": "^7.0.0",
"core-js": "^3.0.1",
"doctrine": "^3.0.0",
"escodegen": "^1.12.0",
"global": "^4.3.2",
"html-tags": "^3.1.0",
"js-string-escape": "^1.0.1",
"lodash": "^4.17.15",
"prop-types": "^15.7.2",
@ -66,7 +73,8 @@
"@types/jest": "^24.0.11",
"@types/prop-types": "^15.5.9",
"@types/util-deprecate": "^1.0.0",
"@types/webpack-env": "^1.14.0"
"@types/webpack-env": "^1.14.0",
"jest-specific-snapshot": "^2.0.0"
},
"peerDependencies": {
"babel-loader": "^8.0.0",

View File

@ -0,0 +1,39 @@
import fs from 'fs';
import { presetsAddPreset, getFrameworks } from '@storybook/postinstall';
// eslint-disable-next-line import/no-extraneous-dependencies
import { logger } from '@storybook/node-logger';
export default function transformer(file, api) {
const packageJson = JSON.parse(fs.readFileSync('./package.json'));
const frameworks = getFrameworks(packageJson);
let err = null;
let framework = null;
let presetOptions = null;
if (frameworks.length !== 1) {
err = `${frameworks.length === 0 ? 'No' : 'Multiple'} frameworks found: ${frameworks}`;
logger.error(`${err}, please configure '@storybook/addon-docs' manually.`);
return file.source;
}
// eslint-disable-next-line prefer-destructuring
framework = frameworks[0];
const { dependencies, devDependencies } = packageJson;
if (
framework === 'react' &&
((dependencies && dependencies['react-scripts']) ||
(devDependencies && devDependencies['react-scripts']))
) {
presetOptions = {
configureJSX: true,
};
}
const j = api.jscodeshift;
const root = j(file.source);
presetsAddPreset(`@storybook/addon-docs/preset`, presetOptions, { root, api });
return root.toSource({ quote: 'single' });
}

View File

@ -2,7 +2,7 @@ import React, { FunctionComponent } from 'react';
import { Description, DescriptionProps as PureDescriptionProps } from '@storybook/components';
import { DocsContext, DocsContextProps } from './DocsContext';
import { Component, CURRENT_SELECTION } from './shared';
import { str } from '../lib/docgenUtils';
import { str } from '../lib/docgen/utils';
export enum DescriptionType {
INFO = 'info',
@ -21,10 +21,10 @@ interface DescriptionProps {
markdown?: string;
}
export const getNotes = (notes?: Notes) =>
const getNotes = (notes?: Notes) =>
notes && (typeof notes === 'string' ? notes : str(notes.markdown) || str(notes.text));
export const getInfo = (info?: Info) => info && (typeof info === 'string' ? info : str(info.text));
const getInfo = (info?: Info) => info && (typeof info === 'string' ? info : str(info.text));
const noDescription = (component?: Component): string | null => null;

View File

@ -0,0 +1,18 @@
import { defaultTitleSlot } from './DocsPage';
describe('defaultTitleSlot', () => {
it('showRoots', () => {
const parameters = {
options: { showRoots: true },
};
expect(defaultTitleSlot({ selectedKind: 'a/b/c', parameters })).toBe('c');
expect(defaultTitleSlot({ selectedKind: 'a|b', parameters })).toBe('a|b');
expect(defaultTitleSlot({ selectedKind: 'a/b/c.d', parameters })).toBe('c.d');
});
it('no showRoots', () => {
const parameters = {};
expect(defaultTitleSlot({ selectedKind: 'a/b/c', parameters })).toBe('c');
expect(defaultTitleSlot({ selectedKind: 'a|b', parameters })).toBe('b');
expect(defaultTitleSlot({ selectedKind: 'a/b/c.d', parameters })).toBe('d');
});
});

View File

@ -47,15 +47,28 @@ interface StoryData {
parameters?: any;
}
const defaultTitleSlot: StringSlot = ({ selectedKind, parameters }) => {
export const defaultTitleSlot: StringSlot = ({ selectedKind, parameters }) => {
const {
showRoots,
hierarchyRootSeparator: rootSeparator,
hierarchySeparator: groupSeparator,
} = (parameters && parameters.options) || {
showRoots: undefined,
hierarchyRootSeparator: '|',
hierarchySeparator: /\/|\./,
};
const { groups } = parseKind(selectedKind, { rootSeparator, groupSeparator });
let groups;
if (typeof showRoots !== 'undefined') {
groups = selectedKind.split('/');
} else {
// This covers off all the remaining cases:
// - If the separators were set above, we should use them
// - If they weren't set, we should only should use the old defaults if the kind contains '.' or '|',
// which for this particular splitting is the only case in which it actually matters.
({ groups } = parseKind(selectedKind, { rootSeparator, groupSeparator }));
}
return (groups && groups[groups.length - 1]) || selectedKind;
};
@ -93,7 +106,7 @@ const DocsStory: FunctionComponent<DocsStoryProps> = ({
parameters,
}) => (
<Anchor storyId={id}>
{expanded && <StoryHeading>{(parameters && parameters.displayName) || name}</StoryHeading>}
{expanded && <StoryHeading>{name}</StoryHeading>}
{expanded && parameters && parameters.docs && parameters.docs.storyDescription && (
<Description markdown={parameters.docs.storyDescription} />
)}

View File

@ -3,7 +3,7 @@ import { PropsTable, PropsTableError, PropsTableProps } from '@storybook/compone
import { DocsContext, DocsContextProps } from './DocsContext';
import { Component, CURRENT_SELECTION } from './shared';
import { PropsExtractor } from '../lib/docgenUtils';
import { PropsExtractor } from '../lib/docgen/types';
import { extractProps as reactExtractProps } from '../frameworks/react/extractProps';
import { extractProps as vueExtractProps } from '../frameworks/vue/extractProps';

View File

@ -104,10 +104,10 @@ export const extractProps = (component: Component) => {
data.forEach((item: Method | Property) => {
const sectionItem: PropDef = {
name: item.name,
type: { name: isMethod(item) ? displaySignature(item) : item.type },
type: { summary: isMethod(item) ? displaySignature(item) : item.type },
required: isMethod(item) ? false : !item.optional,
description: item.description,
defaultValue: isMethod(item) ? '' : item.defaultValue,
defaultValue: { summary: isMethod(item) ? '' : item.defaultValue },
};
const section = mapItemToSection(key, item);

View File

@ -1 +1 @@
export * from '../../lib/docgenUtils';
export * from '../../lib/docgen';

View File

@ -2,7 +2,7 @@
import { addParameters } from '@storybook/client-api';
import { StoryFn } from '@storybook/addons';
import { extractProps } from './extractProps';
import { extractComponentDescription } from '../../lib/docgenUtils';
import { extractComponentDescription } from '../../lib/docgen';
addParameters({
docs: {

View File

@ -1,12 +1,9 @@
import PropTypes from 'prop-types';
import { isForwardRef, isMemo } from 'react-is';
import { PropDef } from '@storybook/components';
import {
PropDefGetter,
PropsExtractor,
extractPropsFromDocgen,
hasDocgen,
} from '../../lib/docgenUtils';
import { hasDocgen, extractPropsFromDocgen, PropsExtractor, TypeSystem } from '../../lib/docgen';
import { Component } from '../../blocks/shared';
import { enhancePropTypesProp } from './propTypes/handleProp';
export interface PropDefMap {
[p: string]: PropDef;
@ -22,21 +19,30 @@ Object.keys(PropTypes).forEach(typeName => {
propTypesMap.set(type.isRequired, typeName);
});
export const getPropDefs: PropDefGetter = (type, section) => {
let processedType = type;
function getPropDefs(component: Component, section: string): PropDef[] {
let processedComponent = component;
// eslint-disable-next-line react/forbid-foreign-prop-types
if (!hasDocgen(type) && !type.propTypes) {
if (isForwardRef(type) || type.render) {
processedType = type.render().type;
if (!hasDocgen(component) && !component.propTypes) {
if (isForwardRef(component) || component.render) {
processedComponent = component.render().type;
}
if (isMemo(type)) {
processedType = type.type().type;
if (isMemo(component)) {
processedComponent = component.type().type;
}
}
return extractPropsFromDocgen(processedType, section);
};
const extractedProps = extractPropsFromDocgen(processedComponent, section);
if (extractedProps.length === 0) {
return [];
}
if (extractedProps[0].typeSystem === TypeSystem.JAVASCRIPT) {
return extractedProps.map(enhancePropTypesProp);
}
return extractedProps.map(x => x.propDef);
}
export const extractProps: PropsExtractor = component => ({
rows: getPropDefs(component, 'props'),

View File

@ -0,0 +1,199 @@
import { parse } from './acornParser';
import {
InspectionType,
InspectionElement,
InspectionObject,
InspectionArray,
InspectionIdentifier,
InspectionLiteral,
InspectionFunction,
InspectionUnknown,
} from './types';
describe('parse', () => {
describe('expression', () => {
it('support HTML element', () => {
const result = parse('<div>Hello!</div>');
const inferedType = result.inferedType as InspectionElement;
expect(inferedType.type).toBe(InspectionType.ELEMENT);
expect(inferedType.identifier).toBe('div');
expect(result.ast).toBeDefined();
});
it('support React declaration', () => {
const result = parse('<FunctionalComponent />');
const inferedType = result.inferedType as InspectionElement;
expect(inferedType.type).toBe(InspectionType.ELEMENT);
expect(inferedType.identifier).toBe('FunctionalComponent');
expect(result.ast).toBeDefined();
});
it('support anonymous functional React component', () => {
const result = parse('() => { return <div>Hey!</div>; }');
const inferedType = result.inferedType as InspectionElement;
expect(inferedType.type).toBe(InspectionType.ELEMENT);
expect(inferedType.identifier).toBeUndefined();
expect(result.ast).toBeDefined();
});
it('support named functional React component', () => {
const result = parse('function NamedFunctionalComponent() { return <div>Hey!</div>; }');
const inferedType = result.inferedType as InspectionElement;
expect(inferedType.type).toBe(InspectionType.ELEMENT);
expect(inferedType.identifier).toBe('NamedFunctionalComponent');
expect(result.ast).toBeDefined();
});
it('support class React component', () => {
const result = parse(`
class ClassComponent extends React.PureComponent {
render() {
return <div>Hey!</div>;
}
}`);
const inferedType = result.inferedType as InspectionElement;
expect(inferedType.type).toBe(InspectionType.ELEMENT);
expect(inferedType.identifier).toBe('ClassComponent');
expect(result.ast).toBeDefined();
});
it('support PropTypes.shape', () => {
const result = parse('PropTypes.shape({ foo: PropTypes.string })');
const inferedType = result.inferedType as InspectionObject;
expect(inferedType.type).toBe(InspectionType.OBJECT);
expect(result.ast).toBeDefined();
});
it('support shape', () => {
const result = parse('shape({ foo: PropTypes.string })');
const inferedType = result.inferedType as InspectionObject;
expect(inferedType.type).toBe(InspectionType.OBJECT);
expect(result.ast).toBeDefined();
});
it('support single prop object literal', () => {
const result = parse('{ foo: PropTypes.string }');
const inferedType = result.inferedType as InspectionObject;
expect(inferedType.type).toBe(InspectionType.OBJECT);
expect(result.ast).toBeDefined();
});
it('support multi prop object literal', () => {
const result = parse(`
{
foo: PropTypes.string,
bar: PropTypes.string
}`);
const inferedType = result.inferedType as InspectionObject;
expect(inferedType.type).toBe(InspectionType.OBJECT);
expect(result.ast).toBeDefined();
});
it('support required prop', () => {
const result = parse('{ foo: PropTypes.string.isRequired }');
const inferedType = result.inferedType as InspectionObject;
expect(inferedType.type).toBe(InspectionType.OBJECT);
expect(result.ast).toBeDefined();
});
it('support array', () => {
const result = parse("['bottom-left', 'botton-center', 'bottom-right']");
const inferedType = result.inferedType as InspectionArray;
expect(inferedType.type).toBe(InspectionType.ARRAY);
expect(result.ast).toBeDefined();
});
it('support object identifier', () => {
const result = parse('NAMED_OBJECT');
const inferedType = result.inferedType as InspectionIdentifier;
expect(inferedType.type).toBe(InspectionType.IDENTIFIER);
expect(inferedType.identifier).toBe('NAMED_OBJECT');
expect(result.ast).toBeDefined();
});
it('support anonymous function', () => {
const result = parse('() => {}');
const inferedType = result.inferedType as InspectionFunction;
expect(inferedType.type).toBe(InspectionType.FUNCTION);
expect(inferedType.identifier).toBeUndefined();
expect(inferedType.hasArguments).toBeFalsy();
expect(result.ast).toBeDefined();
});
it('support anonymous function with arguments', () => {
const result = parse('(a, b) => {}');
const inferedType = result.inferedType as InspectionFunction;
expect(inferedType.type).toBe(InspectionType.FUNCTION);
expect(inferedType.identifier).toBeUndefined();
expect(inferedType.hasArguments).toBeTruthy();
expect(result.ast).toBeDefined();
});
it('support named function', () => {
const result = parse('function concat() {}');
const inferedType = result.inferedType as InspectionFunction;
expect(inferedType.type).toBe(InspectionType.FUNCTION);
expect(inferedType.identifier).toBe('concat');
expect(inferedType.hasArguments).toBeFalsy();
expect(result.ast).toBeDefined();
});
it('support named function with arguments', () => {
const result = parse('function concat(a, b) {}');
const inferedType = result.inferedType as InspectionFunction;
expect(inferedType.type).toBe(InspectionType.FUNCTION);
expect(inferedType.identifier).toBe('concat');
expect(inferedType.hasArguments).toBeTruthy();
expect(result.ast).toBeDefined();
});
it('support class', () => {
const result = parse('class Foo {}');
const inferedType = result.inferedType as InspectionFunction;
expect(inferedType.type).toBe(InspectionType.CLASS);
expect(inferedType.identifier).toBe('Foo');
expect(result.ast).toBeDefined();
});
[
{ name: 'string', value: "'string value'" },
{ name: 'numeric', value: '1' },
{ name: 'boolean (true)', value: 'true' },
{ name: 'boolean (false)', value: 'false' },
{ name: 'null', value: 'null' },
].forEach(x => {
it(`support ${x.name}`, () => {
const result = parse(x.value);
const inferedType = result.inferedType as InspectionLiteral;
expect(inferedType.type).toBe(InspectionType.LITERAL);
expect(result.ast).toBeDefined();
});
});
it("returns Unknown when it's not supported", () => {
const result = parse("Symbol('foo')");
const inferedType = result.inferedType as InspectionUnknown;
expect(inferedType.type).toBe(InspectionType.UNKNOWN);
expect(result.ast).toBeDefined();
});
});
});

View File

@ -0,0 +1,211 @@
import { Parser } from 'acorn';
// @ts-ignore
import jsx from 'acorn-jsx';
import { isNil } from 'lodash';
import estree from 'estree';
// @ts-ignore
import * as acornWalk from 'acorn-walk';
import {
InspectionType,
InspectionInferedType,
InspectionLiteral,
InspectionElement,
InspectionFunction,
InspectionClass,
InspectionObject,
InspectionUnknown,
InspectionIdentifier,
InspectionArray,
} from './types';
interface ParsingResult<T> {
inferedType: T;
ast: any;
}
const ACORN_WALK_VISITORS = {
...acornWalk.base,
JSXElement: () => {},
};
const acornParser = Parser.extend(jsx());
// Cannot use "estree.Identifier" type because this function also support "JSXIdentifier".
function extractIdentifierName(identifierNode: any) {
return !isNil(identifierNode) ? identifierNode.name : null;
}
function parseIdentifier(identifierNode: estree.Identifier): ParsingResult<InspectionIdentifier> {
return {
inferedType: {
type: InspectionType.IDENTIFIER,
identifier: extractIdentifierName(identifierNode),
},
ast: identifierNode,
};
}
function parseLiteral(literalNode: estree.Literal): ParsingResult<InspectionLiteral> {
return {
inferedType: { type: InspectionType.LITERAL },
ast: literalNode,
};
}
function parseFunction(
funcNode: estree.FunctionExpression | estree.ArrowFunctionExpression
): ParsingResult<InspectionFunction | InspectionElement> {
let innerJsxElementNode;
// If there is at least a JSXElement in the body of the function, then it's a React component.
acornWalk.simple(
funcNode.body,
{
JSXElement(node: any) {
innerJsxElementNode = node;
},
},
ACORN_WALK_VISITORS
);
const isJsx = !isNil(innerJsxElementNode);
const inferedType: InspectionFunction | InspectionElement = {
type: isJsx ? InspectionType.ELEMENT : InspectionType.FUNCTION,
hasArguments: funcNode.params.length !== 0,
};
const identifierName = extractIdentifierName((funcNode as estree.FunctionExpression).id);
if (!isNil(identifierName)) {
inferedType.identifier = identifierName;
}
return {
inferedType,
ast: funcNode,
};
}
function parseClass(
classNode: estree.ClassExpression
): ParsingResult<InspectionClass | InspectionElement> {
let innerJsxElementNode;
// If there is at least a JSXElement in the body of the class, then it's a React component.
acornWalk.simple(
classNode.body,
{
JSXElement(node: any) {
innerJsxElementNode = node;
},
},
ACORN_WALK_VISITORS
);
const inferedType: any = {
type: !isNil(innerJsxElementNode) ? InspectionType.ELEMENT : InspectionType.CLASS,
identifier: extractIdentifierName(classNode.id),
};
return {
inferedType,
ast: classNode,
};
}
function parseJsxElement(jsxElementNode: any): ParsingResult<InspectionElement> {
const inferedType: InspectionElement = {
type: InspectionType.ELEMENT,
};
const identifierName = extractIdentifierName(jsxElementNode.openingElement.name);
if (!isNil(identifierName)) {
inferedType.identifier = identifierName;
}
return {
inferedType,
ast: jsxElementNode,
};
}
function parseCall(callNode: estree.CallExpression): ParsingResult<InspectionObject> {
const identifierNode =
callNode.callee.type === 'MemberExpression' ? callNode.callee.property : callNode.callee;
const identifierName = extractIdentifierName(identifierNode);
if (identifierName === 'shape') {
return {
inferedType: { type: InspectionType.OBJECT },
ast: callNode.arguments[0],
};
}
return null;
}
function parseObject(objectNode: estree.ObjectExpression): ParsingResult<InspectionObject> {
return {
inferedType: { type: InspectionType.OBJECT },
ast: objectNode,
};
}
function parseArray(arrayNode: estree.ArrayExpression): ParsingResult<InspectionArray> {
return {
inferedType: { type: InspectionType.ARRAY },
ast: arrayNode,
};
}
// Cannot set "expression" type to "estree.Expression" because the type doesn't include JSX.
function parseExpression(expression: any): ParsingResult<InspectionInferedType> {
switch (expression.type) {
case 'Identifier':
return parseIdentifier(expression);
case 'Literal':
return parseLiteral(expression);
case 'FunctionExpression':
case 'ArrowFunctionExpression':
return parseFunction(expression);
case 'ClassExpression':
return parseClass(expression);
case 'JSXElement':
return parseJsxElement(expression);
case 'CallExpression':
return parseCall(expression);
case 'ObjectExpression':
return parseObject(expression);
case 'ArrayExpression':
return parseArray(expression);
default:
return null;
}
}
export function parse(value: string): ParsingResult<InspectionInferedType> {
const ast = (acornParser.parse(`(${value})`) as unknown) as estree.Program;
let parsingResult: ParsingResult<InspectionUnknown> = {
inferedType: { type: InspectionType.UNKNOWN },
ast,
};
if (!isNil(ast.body[0])) {
const rootNode = ast.body[0];
switch (rootNode.type) {
case 'ExpressionStatement': {
const expressionResult = parseExpression(rootNode.expression);
if (!isNil(expressionResult)) {
parsingResult = expressionResult as any;
}
break;
}
default:
break;
}
}
return parsingResult;
}

View File

@ -0,0 +1,14 @@
import { parse } from './acornParser';
import { InspectionResult, InspectionType } from './types';
export function inspectValue(value: string): InspectionResult {
try {
const parsingResult = parse(value);
return { ...parsingResult };
} catch (e) {
// do nothing.
}
return { inferedType: { type: InspectionType.UNKNOWN } };
}

View File

@ -0,0 +1,79 @@
export enum InspectionType {
IDENTIFIER = 'Identifier',
LITERAL = 'Literal',
OBJECT = 'Object',
ARRAY = 'Array',
FUNCTION = 'Function',
CLASS = 'Class',
ELEMENT = 'Element',
UNKNOWN = 'Unknown',
}
export interface BaseInspectionInferedType {
type: InspectionType;
}
// TODO: Fix this.
// export interface OptionalIdentifierInspectionType extends BaseInspectionInferedType {
// identifier?: string;
// }
// export interface RequiredIdentifierInspectionType extends BaseInspectionInferedType {
// identifier: string;
// }
// export type IdentifiableInspectionType =
// | OptionalIdentifierInspectionType
// | RequiredIdentifierInspectionType;
export interface InspectionIdentifier extends BaseInspectionInferedType {
type: InspectionType.IDENTIFIER;
identifier: string;
}
export interface InspectionLiteral extends BaseInspectionInferedType {
type: InspectionType.LITERAL;
}
export interface InspectionObject extends BaseInspectionInferedType {
type: InspectionType.OBJECT;
}
export interface InspectionArray extends BaseInspectionInferedType {
type: InspectionType.ARRAY;
}
export interface InspectionClass extends BaseInspectionInferedType {
type: InspectionType.CLASS;
identifier: string;
}
export interface InspectionFunction extends BaseInspectionInferedType {
type: InspectionType.FUNCTION;
identifier?: string;
hasArguments: boolean;
}
export interface InspectionElement extends BaseInspectionInferedType {
type: InspectionType.ELEMENT;
identifier?: string;
}
export interface InspectionUnknown extends BaseInspectionInferedType {
type: InspectionType.UNKNOWN;
}
export type InspectionInferedType =
| InspectionIdentifier
| InspectionLiteral
| InspectionObject
| InspectionArray
| InspectionClass
| InspectionFunction
| InspectionElement
| InspectionUnknown;
export interface InspectionResult {
inferedType: InspectionInferedType;
ast?: any;
}

View File

@ -0,0 +1,6 @@
export const CUSTOM_CAPTION = 'custom';
export const OBJECT_CAPTION = 'object';
export const ARRAY_CAPTION = 'array';
export const CLASS_CAPTION = 'class';
export const FUNCTION_CAPTION = 'func';
export const ELEMENT_CAPTION = 'element';

View File

@ -0,0 +1,115 @@
import { isNil } from 'lodash';
// @ts-ignore
import { PropDefaultValue, PropSummaryValue } from '@storybook/components';
import { inspectValue } from '../inspection/inspectValue';
import { OBJECT_CAPTION, FUNCTION_CAPTION, ELEMENT_CAPTION, ARRAY_CAPTION } from './captions';
import { generateCode } from './generateCode';
import {
InspectionFunction,
InspectionResult,
InspectionType,
InspectionElement,
} from '../inspection/types';
import { isHtmlTag } from './isHtmlTag';
const MAX_SUMMARY_LENGTH = 50;
function isTooLongForSummary(value: string): boolean {
return value.length > MAX_SUMMARY_LENGTH;
}
// TODO: Fix this any type.
function getPrettyIdentifier(inferedType: any): string {
const { type, identifier } = inferedType;
switch (type) {
case InspectionType.FUNCTION:
return inferedType.hasArguments ? `${identifier}( ... )` : `${identifier}()`;
case InspectionType.ELEMENT:
return `<${identifier} />`;
default:
return identifier;
}
}
function createSummaryValue(summary: string, detail?: string): PropSummaryValue {
return { summary, detail };
}
function generateObject({ ast }: InspectionResult): PropDefaultValue {
let prettyCaption = generateCode(ast, true);
// Cannot get escodegen to add a space before the last } with the compact mode settings.
// This fix it until a better solution is found.
if (!prettyCaption.endsWith(' }')) {
prettyCaption = `${prettyCaption.slice(0, -1)} }`;
}
return !isTooLongForSummary(prettyCaption)
? createSummaryValue(prettyCaption)
: createSummaryValue(OBJECT_CAPTION, generateCode(ast));
}
function generateFunc({ inferedType, ast }: InspectionResult): PropDefaultValue {
const { identifier } = inferedType as InspectionFunction;
if (!isNil(identifier)) {
return createSummaryValue(getPrettyIdentifier(inferedType), generateCode(ast));
}
const prettyCaption = generateCode(ast, true);
return !isTooLongForSummary(prettyCaption)
? createSummaryValue(prettyCaption)
: createSummaryValue(FUNCTION_CAPTION, generateCode(ast));
}
// All elements are JSX elements.
// JSX elements cannot are not supported by escodegen.
function generateElement(
defaultValue: string,
inspectionResult: InspectionResult
): PropDefaultValue {
const { inferedType } = inspectionResult;
const { identifier } = inferedType as InspectionElement;
if (!isNil(identifier)) {
if (!isHtmlTag(identifier)) {
const prettyIdentifier = getPrettyIdentifier(inferedType);
return createSummaryValue(
prettyIdentifier,
prettyIdentifier !== defaultValue ? defaultValue : undefined
);
}
}
return !isTooLongForSummary(defaultValue)
? createSummaryValue(defaultValue)
: createSummaryValue(ELEMENT_CAPTION, defaultValue);
}
function generateArray({ ast }: InspectionResult): PropDefaultValue {
const prettyCaption = generateCode(ast, true);
return !isTooLongForSummary(prettyCaption)
? createSummaryValue(prettyCaption)
: createSummaryValue(ARRAY_CAPTION, generateCode(ast));
}
export function createDefaultValue(defaultValue: string): PropDefaultValue {
const inspectionResult = inspectValue(defaultValue);
switch (inspectionResult.inferedType.type) {
case InspectionType.OBJECT:
return generateObject(inspectionResult);
case InspectionType.FUNCTION:
return generateFunc(inspectionResult);
case InspectionType.ELEMENT:
return generateElement(defaultValue, inspectionResult);
case InspectionType.ARRAY:
return generateArray(inspectionResult);
default:
return null;
}
}

View File

@ -0,0 +1,366 @@
import { isNil } from 'lodash';
import { PropSummaryValue, PropType } from '@storybook/components';
import { ExtractedProp, DocgenPropType } from '../../../lib/docgen';
import { inspectValue } from '../inspection/inspectValue';
import { generateCode } from './generateCode';
import { generateFuncSignature } from './generateFuncSignature';
import {
OBJECT_CAPTION,
ARRAY_CAPTION,
CLASS_CAPTION,
FUNCTION_CAPTION,
ELEMENT_CAPTION,
CUSTOM_CAPTION,
} from './captions';
import { InspectionType } from '../inspection/types';
import { isHtmlTag } from './isHtmlTag';
const MAX_SUMMARY_LENGTH = 35;
enum PropTypesType {
CUSTOM = 'custom',
ANY = 'any',
FUNC = 'func',
SHAPE = 'shape',
OBJECT = 'object',
INSTANCEOF = 'instanceOf',
OBJECTOF = 'objectOf',
UNION = 'union',
ENUM = 'enum',
ARRAYOF = 'arrayOf',
ELEMENT = 'element',
ELEMENTTYPE = 'elementType',
NODE = 'node',
}
interface EnumValue {
value: string;
computed: boolean;
}
interface TypeDef {
name: string;
value: PropSummaryValue;
inferedType?: InspectionType;
}
function createTypeDef({
name,
summary,
detail,
inferedType,
}: {
name: string;
summary: string;
detail?: string;
inferedType?: InspectionType;
}): TypeDef {
return {
name,
value: {
summary,
detail: !isNil(detail) ? detail : summary,
},
inferedType,
};
}
function cleanPropTypes(value: string): string {
return value.replace(/PropTypes./g, '').replace(/.isRequired/g, '');
}
function prettyObject(ast: any, compact = false): string {
return cleanPropTypes(generateCode(ast, compact));
}
function getCaptionFromInspectionType(type: InspectionType): string {
switch (type) {
case InspectionType.OBJECT:
return OBJECT_CAPTION;
case InspectionType.ARRAY:
return ARRAY_CAPTION;
case InspectionType.CLASS:
return CLASS_CAPTION;
case InspectionType.FUNCTION:
return FUNCTION_CAPTION;
case InspectionType.ELEMENT:
return ELEMENT_CAPTION;
default:
return CUSTOM_CAPTION;
}
}
function isTooLongForSummary(value: string): boolean {
return value.length > MAX_SUMMARY_LENGTH;
}
function generateValuesForObjectAst(ast: any): [string, string] {
let summary = prettyObject(ast, true);
let detail;
if (!isTooLongForSummary(summary)) {
detail = summary;
} else {
summary = OBJECT_CAPTION;
detail = prettyObject(ast);
}
return [summary, detail];
}
function generateCustom({ raw }: DocgenPropType): TypeDef {
if (!isNil(raw)) {
const { inferedType, ast } = inspectValue(raw);
const { type, identifier } = inferedType as any;
let summary;
let detail;
switch (type) {
case InspectionType.IDENTIFIER:
case InspectionType.LITERAL:
summary = raw;
break;
case InspectionType.OBJECT: {
const [objectCaption, objectValue] = generateValuesForObjectAst(ast);
summary = objectCaption;
detail = objectValue;
break;
}
case InspectionType.ELEMENT:
summary = !isNil(identifier) && !isHtmlTag(identifier) ? identifier : ELEMENT_CAPTION;
detail = raw;
break;
default:
summary = getCaptionFromInspectionType(type);
detail = raw;
break;
}
return createTypeDef({
name: PropTypesType.CUSTOM,
summary,
detail,
inferedType: type,
});
}
return createTypeDef({ name: PropTypesType.CUSTOM, summary: CUSTOM_CAPTION });
}
function generateFunc(extractedProp: ExtractedProp): TypeDef {
const { jsDocTags } = extractedProp;
if (!isNil(jsDocTags)) {
if (!isNil(jsDocTags.params) || !isNil(jsDocTags.returns)) {
return createTypeDef({
name: PropTypesType.FUNC,
summary: FUNCTION_CAPTION,
detail: generateFuncSignature(jsDocTags.params, jsDocTags.returns),
});
}
}
return createTypeDef({ name: PropTypesType.FUNC, summary: FUNCTION_CAPTION });
}
function generateShape(type: DocgenPropType, extractedProp: ExtractedProp): TypeDef {
const fields = Object.keys(type.value)
.map((key: string) => `${key}: ${generateType(type.value[key], extractedProp).value.detail}`)
.join(', ');
const { ast } = inspectValue(`{ ${fields} }`);
const [summary, detail] = generateValuesForObjectAst(ast);
return createTypeDef({
name: PropTypesType.SHAPE,
summary,
detail,
});
}
function generateObjectOf(type: DocgenPropType, extractedProp: ExtractedProp): TypeDef {
const format = (of: string) => `objectOf(${of})`;
const { name, value } = generateType(type.value, extractedProp);
// eslint-disable-next-line prefer-const
let { summary, detail } = value;
if (name === PropTypesType.SHAPE) {
if (!isTooLongForSummary(detail)) {
summary = detail;
}
}
return createTypeDef({
name: PropTypesType.OBJECTOF,
summary: format(summary),
detail: format(detail),
});
}
function generateUnion(type: DocgenPropType, extractedProp: ExtractedProp): TypeDef {
if (Array.isArray(type.value)) {
const values = type.value.reduce(
(acc: any, v: any) => {
const { summary, detail } = generateType(v, extractedProp).value;
acc.summary.push(summary);
acc.detail.push(detail);
return acc;
},
{ summary: [], detail: [] }
);
return createTypeDef({
name: PropTypesType.UNION,
summary: values.summary.join(' | '),
detail: values.detail.join(' | '),
});
}
return createTypeDef({ name: PropTypesType.UNION, summary: type.value });
}
function generateEnumValue({ value, computed }: EnumValue): TypeDef {
if (computed) {
const { inferedType, ast } = inspectValue(value) as any;
const { type } = inferedType;
let caption = getCaptionFromInspectionType(type);
if (
type === InspectionType.FUNCTION ||
type === InspectionType.CLASS ||
type === InspectionType.ELEMENT
) {
if (!isNil(inferedType.identifier)) {
caption = inferedType.identifier;
}
}
return createTypeDef({
name: 'enumvalue',
summary: caption,
detail: type === InspectionType.OBJECT ? prettyObject(ast) : value,
inferedType: type,
});
}
return createTypeDef({ name: 'enumvalue', summary: value });
}
function generateEnum(type: DocgenPropType): TypeDef {
if (Array.isArray(type.value)) {
const values = type.value.reduce(
(acc: any, v: EnumValue) => {
const { summary, detail } = generateEnumValue(v).value;
acc.summary.push(summary);
acc.detail.push(detail);
return acc;
},
{ summary: [], detail: [] }
);
return createTypeDef({
name: PropTypesType.ENUM,
summary: values.summary.join(' | '),
detail: values.detail.join(' | '),
});
}
return createTypeDef({ name: PropTypesType.ENUM, summary: type.value });
}
function braceAfter(of: string): string {
return `${of}[]`;
}
function braceAround(of: string): string {
return `[${of}]`;
}
function createArrayOfObjectTypeDef(summary: string, detail: string): TypeDef {
return createTypeDef({
name: PropTypesType.ARRAYOF,
summary: summary === OBJECT_CAPTION ? braceAfter(summary) : braceAround(summary),
detail: braceAround(detail),
});
}
function generateArray(type: DocgenPropType, extractedProp: ExtractedProp): TypeDef {
const { name, value, inferedType } = generateType(type.value, extractedProp);
const { summary, detail } = value;
if (name === PropTypesType.CUSTOM) {
if (inferedType === InspectionType.OBJECT) {
return createArrayOfObjectTypeDef(summary, detail);
}
} else if (name === PropTypesType.SHAPE) {
return createArrayOfObjectTypeDef(summary, detail);
}
return createTypeDef({ name: PropTypesType.ARRAYOF, summary: braceAfter(detail) });
}
function generateType(type: DocgenPropType, extractedProp: ExtractedProp): TypeDef {
try {
switch (type.name) {
case PropTypesType.CUSTOM:
return generateCustom(type);
case PropTypesType.FUNC:
return generateFunc(extractedProp);
case PropTypesType.SHAPE:
return generateShape(type, extractedProp);
case PropTypesType.INSTANCEOF:
return createTypeDef({ name: PropTypesType.INSTANCEOF, summary: type.value });
case PropTypesType.OBJECTOF:
return generateObjectOf(type, extractedProp);
case PropTypesType.UNION:
return generateUnion(type, extractedProp);
case PropTypesType.ENUM:
return generateEnum(type);
case PropTypesType.ARRAYOF:
return generateArray(type, extractedProp);
default:
return createTypeDef({ name: type.name, summary: type.name });
}
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
}
return createTypeDef({ name: 'unknown', summary: 'unknown' });
}
export function createType(extractedProp: ExtractedProp): PropType {
const { type } = extractedProp.docgenInfo;
switch (type.name) {
case PropTypesType.CUSTOM:
case PropTypesType.SHAPE:
case PropTypesType.INSTANCEOF:
case PropTypesType.OBJECTOF:
case PropTypesType.UNION:
case PropTypesType.ENUM:
case PropTypesType.ARRAYOF: {
const { summary, detail } = generateType(type, extractedProp).value;
return {
summary,
detail: summary !== detail ? detail : undefined,
};
}
case PropTypesType.FUNC: {
const { detail } = generateType(type, extractedProp).value;
return { summary: detail };
}
default:
return null;
}
}

View File

@ -0,0 +1,25 @@
import { generate } from 'escodegen';
const BASIC_OPTIONS = {
format: {
indent: {
style: ' ',
},
semicolons: false,
},
};
const COMPACT_OPTIONS = {
...BASIC_OPTIONS,
format: {
newline: '',
},
};
const PRETTY_OPTIONS = {
...BASIC_OPTIONS,
};
export function generateCode(ast: any, compact = false): string {
return generate(ast, compact ? COMPACT_OPTIONS : PRETTY_OPTIONS);
}

View File

@ -0,0 +1,156 @@
import { generateFuncSignature } from './generateFuncSignature';
import { parseJsDoc } from '../../../lib/jsdocParser';
it('should return an empty string with there is no @params and @returns tags', () => {
const result = generateFuncSignature(null, null);
expect(result).toBe('');
});
it('should return a signature with a single arg when there is a @param tag with a name', () => {
const { params, returns } = parseJsDoc('@param event').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('(event)');
});
it('should return a signature with a single arg when there is a @param tag with a name and a type', () => {
const { params, returns } = parseJsDoc('@param {SyntheticEvent} event').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('(event: SyntheticEvent)');
});
it('should return a signature with a single arg when there is a @param tag with a name, a type and a desc', () => {
const { params, returns } = parseJsDoc(
'@param {SyntheticEvent} event - React event'
).extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('(event: SyntheticEvent)');
});
it('should support @param of record type', () => {
const { params, returns } = parseJsDoc('@param {{a: number}} event').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('(event: ({a: number}))');
});
it('should support @param of union type', () => {
const { params, returns } = parseJsDoc('@param {(number|boolean)} event').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('(event: (number|boolean))');
});
it('should support @param of array type', () => {
const { params, returns } = parseJsDoc('@param {number[]} event').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('(event: number[])');
});
it('should support @param with a nullable type', () => {
const { params, returns } = parseJsDoc('@param {?number} event').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('(event: number)');
});
it('should support @param with a non nullable type', () => {
const { params, returns } = parseJsDoc('@param {!number} event').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('(event: number)');
});
it('should support optional @param with []', () => {
const { params, returns } = parseJsDoc('@param {number} [event]').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('(event: number)');
});
it('should support optional @param with =', () => {
const { params, returns } = parseJsDoc('@param {number=} event').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('(event: number)');
});
it('should support @param of type any', () => {
const { params, returns } = parseJsDoc('@param {*} event').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('(event: any)');
});
it('should support multiple @param tags', () => {
const { params, returns } = parseJsDoc(
'@param {SyntheticEvent} event\n@param {string} customData'
).extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('(event: SyntheticEvent, customData: string)');
});
it('should return a signature with a return type when there is a @returns with a type', () => {
const { params, returns } = parseJsDoc('@returns {string}').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('() => string');
});
it('should support @returns of record type', () => {
const { params, returns } = parseJsDoc('@returns {{a: number, b: string}}').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('() => ({a: number, b: string})');
});
it('should support @returns of array type', () => {
const { params, returns } = parseJsDoc('@returns {integer[]}').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('() => integer[]');
});
it('should support @returns of union type', () => {
const { params, returns } = parseJsDoc('@returns {(number|boolean)}').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('() => (number|boolean)');
});
it('should support @returns type any', () => {
const { params, returns } = parseJsDoc('@returns {*}').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('() => any');
});
it('should support @returns of type void', () => {
const { params, returns } = parseJsDoc('@returns {void}').extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('() => void');
});
it('should return a full signature when there is a single @param tag and a @returns', () => {
const { params, returns } = parseJsDoc(
'@param {SyntheticEvent} event - React event.\n@returns {string}'
).extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('(event: SyntheticEvent) => string');
});
it('should return a full signature when there is a multiple @param tags and a @returns', () => {
const { params, returns } = parseJsDoc(
'@param {SyntheticEvent} event - React event.\n@param {string} data\n@returns {string}'
).extractedTags;
const result = generateFuncSignature(params, returns);
expect(result).toBe('(event: SyntheticEvent, data: string) => string');
});

View File

@ -0,0 +1,39 @@
import { isNil } from 'lodash';
import { ExtractedJsDocParam, ExtractedJsDocReturns } from '../../../lib/jsdocParser';
export function generateFuncSignature(
params: ExtractedJsDocParam[],
returns: ExtractedJsDocReturns
): string {
const hasParams = !isNil(params);
const hasReturns = !isNil(returns);
if (!hasParams && !hasReturns) {
return '';
}
const funcParts = [];
if (hasParams) {
const funcParams = params.map((x: ExtractedJsDocParam) => {
const prettyName = x.getPrettyName();
const typeName = x.getTypeName();
if (!isNil(typeName)) {
return `${prettyName}: ${typeName}`;
}
return prettyName;
});
funcParts.push(`(${funcParams.join(', ')})`);
} else {
funcParts.push('()');
}
if (hasReturns) {
funcParts.push(`=> ${returns.getTypeName()}`);
}
return funcParts.join(' ');
}

View File

@ -0,0 +1,673 @@
/* eslint-disable no-underscore-dangle */
import { PropDef } from '@storybook/components';
import { Component } from '../../../blocks/shared';
import { extractPropsFromDocgen } from '../../../lib/docgen';
import { enhancePropTypesProp } from './handleProp';
const DOCGEN_SECTION = 'props';
const PROP_NAME = 'propName';
function createComponent(docgenInfo: Record<string, any>): Component {
const component = () => {};
// @ts-ignore
component.__docgenInfo = {
[DOCGEN_SECTION]: {
[PROP_NAME]: {
required: false,
...docgenInfo,
},
},
};
return component;
}
function extractPropDef(component: Component): PropDef {
return enhancePropTypesProp(extractPropsFromDocgen(component, DOCGEN_SECTION)[0]);
}
describe('prop type', () => {
describe('custom', () => {
describe('when raw value is available', () => {
it('should support literal', () => {
const component = createComponent({
type: {
name: 'custom',
raw: 'MY_LITERAL',
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('MY_LITERAL');
expect(type.detail).toBeUndefined();
});
it('should support short object', () => {
const component = createComponent({
type: {
name: 'custom',
raw: '{\n text: PropTypes.string.isRequired,\n}',
},
});
const { type } = extractPropDef(component);
const expectedSummary = `{
text: string
}`;
expect(type.summary.replace(/\s/g, '')).toBe(expectedSummary.replace(/\s/g, ''));
expect(type.detail).toBeUndefined();
});
it('should support long object', () => {
const component = createComponent({
type: {
name: 'custom',
raw:
'{\n text: PropTypes.string.isRequired,\n value: PropTypes.string.isRequired,\n}',
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('object');
const expectedDetail = `{
text: string,
value: string
}`;
expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
});
it('should use identifier of a React element when available', () => {
const component = createComponent({
type: {
name: 'custom',
raw:
'function InlinedFunctionalComponent() {\n return <div>Inlined FunctionnalComponent!</div>;\n}',
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('InlinedFunctionalComponent');
const expectedDetail = `function InlinedFunctionalComponent() {
return <div>Inlined FunctionnalComponent!</div>;
}`;
expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
});
it('should not use identifier of a HTML element', () => {
const component = createComponent({
type: {
name: 'custom',
raw: '<div>Hello world!</div>',
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('element');
const expectedDetail = '<div>Hello world!</div>';
expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
});
it('should support element without identifier', () => {
const component = createComponent({
type: {
name: 'custom',
raw: '() => {\n return <div>Inlined FunctionnalComponent!</div>;\n}',
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('element');
const expectedDetail = `() => {
return <div>Inlined FunctionnalComponent!</div>;
}`;
expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
});
it('should return "custom" when it is not a known type', () => {
const component = createComponent({
type: {
name: 'custom',
raw: 'Symbol("Hey!")',
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('custom');
});
});
it("should return 'custom' when there is no raw value", () => {
const component = createComponent({
type: {
name: 'custom',
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('custom');
});
});
['any', 'bool', 'string', 'number', 'symbol', 'object', 'element', 'elementType', 'node'].forEach(
x => {
it(`should return '${x}' when type is ${x}`, () => {
const component = createComponent({
type: {
name: x,
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe(x);
});
}
);
it('should support short shape', () => {
const component = createComponent({
type: {
name: 'shape',
value: {
foo: {
name: 'string',
required: false,
},
},
},
});
const { type } = extractPropDef(component);
const expectedSummary = '{ foo: string }';
expect(type.summary.replace(/\s/g, '')).toBe(expectedSummary.replace(/\s/g, ''));
expect(type.detail).toBeUndefined();
});
it('should support long shape', () => {
const component = createComponent({
type: {
name: 'shape',
value: {
foo: {
name: 'string',
required: false,
},
bar: {
name: 'string',
required: false,
},
another: {
name: 'string',
required: false,
},
},
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('object');
const expectedDetail = `{
foo: string,
bar: string,
another: string
}`;
expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
});
it('should support enum of string', () => {
const component = createComponent({
type: {
name: 'enum',
value: [
{
value: "'News'",
computed: false,
},
{
value: "'Photos'",
computed: false,
},
],
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe("'News' | 'Photos'");
});
it('should support enum of object', () => {
const component = createComponent({
type: {
name: 'enum',
value: [
{
value:
'{\n text: PropTypes.string.isRequired,\n value: PropTypes.string.isRequired,\n}',
computed: true,
},
{
value: '{\n foo: PropTypes.string,\n bar: PropTypes.string,\n}',
computed: true,
},
],
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('object | object');
const expectedDetail = `{
text: string,
value: string
} | {
foo: string,
bar: string
}`;
expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
});
it('should support enum of element', () => {
const component = createComponent({
type: {
name: 'enum',
value: [
{
value: '() => {\n return <div>FunctionnalComponent!</div>;\n}',
computed: true,
},
{
value:
'class ClassComponent extends React.PureComponent {\n render() {\n return <div>ClassComponent!</div>;\n }\n}',
computed: true,
},
],
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('element | ClassComponent');
const expectedDetail = `() => {
return <div>FunctionnalComponent!</div>;
} | class ClassComponent extends React.PureComponent {
render() {
return <div>ClassComponent!</div>;
}
}`;
expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
});
describe('func', () => {
it('should return "func" when the prop dont have a description', () => {
const component = createComponent({
type: {
name: 'func',
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('func');
});
it('should return "func" when the prop have a description without JSDoc tags', () => {
const component = createComponent({
type: {
name: 'func',
},
description: 'Hey! Hey!',
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('func');
});
it('should return a func signature when there is JSDoc tags.', () => {
const component = createComponent({
type: {
name: 'func',
},
description: '@param event\n@param data\n@returns {string}',
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('(event, data) => string');
});
});
it('should return the instance type when type is instanceOf', () => {
const component = createComponent({
type: {
name: 'instanceOf',
value: 'Set',
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('Set');
});
describe('objectOf', () => {
it('should support objectOf primitive', () => {
const component = createComponent({
type: {
name: 'objectOf',
value: {
name: 'number',
},
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('objectOf(number)');
expect(type.detail).toBeUndefined();
});
it('should support objectOf of identifier', () => {
const component = createComponent({
type: {
name: 'objectOf',
value: {
name: 'custom',
raw: 'NAMED_OBJECT',
},
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('objectOf(NAMED_OBJECT)');
expect(type.detail).toBeUndefined();
});
it('should support objectOf short object', () => {
const component = createComponent({
type: {
name: 'objectOf',
value: {
name: 'custom',
raw: '{\n foo: PropTypes.string,\n}',
},
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('objectOf({ foo: string })');
expect(type.detail).toBeUndefined();
});
it('should support objectOf long object', () => {
const component = createComponent({
type: {
name: 'objectOf',
value: {
name: 'custom',
raw:
'{\n foo: PropTypes.string,\n bar: PropTypes.string,\n another: PropTypes.string,\n}',
},
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('objectOf(object)');
const expectedDetail = `objectOf({
foo: string,
bar: string,
another: string
})`;
expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
});
it('should support objectOf short shape', () => {
const component = createComponent({
type: {
name: 'objectOf',
value: {
name: 'shape',
value: {
foo: {
name: 'string',
required: false,
},
},
},
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('objectOf({ foo: string })');
expect(type.detail).toBeUndefined();
});
it('should support objectOf long shape', () => {
const component = createComponent({
type: {
name: 'objectOf',
value: {
name: 'shape',
value: {
foo: {
name: 'string',
required: false,
},
bar: {
name: 'string',
required: false,
},
another: {
name: 'string',
required: false,
},
},
},
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('objectOf(object)');
const expectedDetail = `objectOf({
foo: string,
bar: string,
another: string
})`;
expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
});
});
it('should support union', () => {
const component = createComponent({
type: {
name: 'union',
value: [
{
name: 'string',
},
{
name: 'instanceOf',
value: 'Set',
},
],
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('string | Set');
expect(type.detail).toBeUndefined();
});
describe('array', () => {
it('should support array of primitive', () => {
const component = createComponent({
type: {
name: 'arrayOf',
value: {
name: 'number',
},
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('number[]');
expect(type.detail).toBeUndefined();
});
it('should support array of identifier', () => {
const component = createComponent({
type: {
name: 'arrayOf',
value: {
name: 'custom',
raw: 'NAMED_OBJECT',
},
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('NAMED_OBJECT[]');
expect(type.detail).toBeUndefined();
});
it('should support array of short object', () => {
const component = createComponent({
type: {
name: 'arrayOf',
value: {
name: 'custom',
raw: '{\n foo: PropTypes.string,\n}',
},
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('[{ foo: string }]');
expect(type.detail).toBeUndefined();
});
it('should support array of long object', () => {
const component = createComponent({
type: {
name: 'arrayOf',
value: {
name: 'custom',
raw:
'{\n text: PropTypes.string.isRequired,\n value: PropTypes.string.isRequired,\n}',
},
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('object[]');
const expectedDetail = `[{
text: string,
value: string
}]`;
expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
});
it('should support array of short shape', () => {
const component = createComponent({
type: {
name: 'arrayOf',
value: {
name: 'shape',
value: {
foo: {
name: 'string',
required: false,
},
},
},
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('[{ foo: string }]');
expect(type.detail).toBeUndefined();
});
it('should support array of long shape', () => {
const component = createComponent({
type: {
name: 'arrayOf',
value: {
name: 'shape',
value: {
foo: {
name: 'string',
required: false,
},
bar: {
name: 'string',
required: false,
},
another: {
name: 'string',
required: false,
},
},
},
},
});
const { type } = extractPropDef(component);
expect(type.summary).toBe('object[]');
const expectedDetail = `[{
foo: string,
bar: string,
another: string
}]`;
expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
});
});
});

View File

@ -0,0 +1,24 @@
import { isNil } from 'lodash';
import { PropDef } from '@storybook/components';
import { ExtractedProp } from '../../../lib/docgen';
import { createType } from './createType';
import { createDefaultValue } from './createDefaultValue';
export function enhancePropTypesProp(extractedProp: ExtractedProp): PropDef {
const { propDef } = extractedProp;
const newtype = createType(extractedProp);
if (!isNil(newtype)) {
propDef.type = newtype;
}
const { defaultValue } = extractedProp.docgenInfo;
if (!isNil(defaultValue)) {
const newDefaultValue = createDefaultValue(defaultValue.value);
if (!isNil(newDefaultValue)) {
propDef.defaultValue = newDefaultValue;
}
}
return propDef;
}

View File

@ -0,0 +1,5 @@
import htmlTags from 'html-tags';
export function isHtmlTag(tagName: string): boolean {
return htmlTags.includes(tagName.toLowerCase());
}

View File

@ -4,7 +4,7 @@ import toReact from '@egoist/vue-to-react';
import { StoryFn } from '@storybook/addons';
import { addParameters } from '@storybook/client-api';
import { extractProps } from './extractProps';
import { extractComponentDescription } from '../../lib/docgenUtils';
import { extractComponentDescription } from '../../lib/docgen/utils';
addParameters({
docs: {

View File

@ -1,5 +1,5 @@
import { PropDef } from '@storybook/components';
import { PropsExtractor, extractPropsFromDocgen, hasDocgen } from '../../lib/docgenUtils';
import { PropsExtractor, hasDocgen, extractPropsFromDocgen } from '../../lib/docgen';
const SECTIONS = ['props', 'events', 'slots'];
@ -9,7 +9,7 @@ export const extractProps: PropsExtractor = component => {
}
const sections: Record<string, PropDef[]> = {};
SECTIONS.forEach(section => {
sections[section] = extractPropsFromDocgen(component, section);
sections[section] = extractPropsFromDocgen(component, section).map(x => x.propDef);
});
return { sections };
};

View File

@ -8,10 +8,10 @@ import { render } from 'lit-html';
function mapData(data) {
return data.map(item => ({
name: item.name,
type: { name: item.type },
type: { summary: item.type },
required: '',
description: item.description,
defaultValue: item.default,
defaultValue: { summary: item.default },
}));
}

View File

@ -1,16 +0,0 @@
export interface DocgenInfo {
type?: {
name: string;
value?: {
name?: string;
raw?: string;
};
};
flowType?: any;
tsType?: any;
required: boolean;
description?: string;
defaultValue?: {
value: string;
};
}

View File

@ -0,0 +1,23 @@
import { isNil } from 'lodash';
import { PropDefaultValue } from '@storybook/components';
import { DocgenPropDefaultValue } from './types';
const BLACKLIST = ['null', 'undefined'];
function isDefaultValueBlacklisted(value: string) {
return BLACKLIST.some(x => x === value);
}
export function createDefaultValue(defaultValue: DocgenPropDefaultValue): PropDefaultValue {
if (!isNil(defaultValue)) {
const { value } = defaultValue;
if (!isDefaultValueBlacklisted(value)) {
return {
summary: value,
};
}
}
return null;
}

View File

@ -0,0 +1,99 @@
import { isNil } from 'lodash';
import { PropDef } from '@storybook/components';
import { TypeSystem, DocgenInfo, DocgenType } from './types';
import { JsDocParsingResult } from '../jsdocParser';
import { createDefaultValue } from './createDefaultValue';
export type PropDefFactory = (
propName: string,
docgenInfo: DocgenInfo,
jsDocParsingResult?: JsDocParsingResult
) => PropDef;
function createBasicPropDef(name: string, type: DocgenType, docgenInfo: DocgenInfo): PropDef {
const { description, required, defaultValue } = docgenInfo;
return {
name,
type: { summary: type.name },
required,
description,
defaultValue: createDefaultValue(defaultValue),
};
}
function createPropDef(
name: string,
type: DocgenType,
docgenInfo: DocgenInfo,
jsDocParsingResult: JsDocParsingResult
): PropDef {
const propDef = createBasicPropDef(name, type, docgenInfo);
if (jsDocParsingResult.includesJsDoc) {
const { description, extractedTags } = jsDocParsingResult;
if (!isNil(description)) {
propDef.description = jsDocParsingResult.description;
}
const hasParams = !isNil(extractedTags.params);
const hasReturns = !isNil(extractedTags.returns) && !isNil(extractedTags.returns.type);
if (hasParams || hasReturns) {
propDef.jsDocTags = {
params:
hasParams &&
extractedTags.params.map(x => ({ name: x.getPrettyName(), description: x.description })),
returns: hasReturns && { description: extractedTags.returns.description },
};
}
}
return propDef;
}
export const javaScriptFactory: PropDefFactory = (
propName: string,
docgenInfo: DocgenInfo,
jsDocParsingResult?: JsDocParsingResult
) => {
return createPropDef(propName, docgenInfo.type, docgenInfo, jsDocParsingResult);
};
export const tsFactory: PropDefFactory = (
propName: string,
docgenInfo: DocgenInfo,
jsDocParsingResult?: JsDocParsingResult
) => {
return createPropDef(propName, docgenInfo.tsType, docgenInfo, jsDocParsingResult);
};
export const flowFactory: PropDefFactory = (
propName: string,
docgenInfo: DocgenInfo,
jsDocParsingResult?: JsDocParsingResult
) => {
return createPropDef(propName, docgenInfo.flowType, docgenInfo, jsDocParsingResult);
};
export const unknownFactory: PropDefFactory = (
propName: string,
docgenInfo: DocgenInfo,
jsDocParsingResult?: JsDocParsingResult
) => {
return createPropDef(propName, { name: 'unknown' }, docgenInfo, jsDocParsingResult);
};
export const getPropDefFactory = (typeSystem: TypeSystem): PropDefFactory => {
switch (typeSystem) {
case TypeSystem.JAVASCRIPT:
return javaScriptFactory;
case TypeSystem.TYPESCRIPT:
return tsFactory;
case TypeSystem.FLOW:
return flowFactory;
default:
return unknownFactory;
}
};

View File

@ -0,0 +1,155 @@
/* eslint-disable no-underscore-dangle */
import { Component } from '../../blocks/shared';
import { extractPropsFromDocgen } from './extractDocgenProps';
const DOCGEN_SECTION = 'props';
const PROP_NAME = 'propName';
interface TypeSystemDef {
name: string;
typeProperty?: string;
}
const TypeSystems: TypeSystemDef[] = [
{ name: 'javascript', typeProperty: 'type' },
{ name: 'typescript', typeProperty: 'tsType' },
];
function createType(typeName: string, others: Record<string, any> = {}): Record<string, string> {
return {
name: typeName,
...others,
};
}
function createStringType(typeSystemDef: TypeSystemDef, others: Record<string, any> = {}): any {
return {
[typeSystemDef.typeProperty]: createType('string', others),
};
}
function createFuncType(typeSystemDef: TypeSystemDef, others: Record<string, any> = {}): any {
const typeName = typeSystemDef.name === 'javascript' ? 'func' : '() => {}';
return {
[typeSystemDef.typeProperty]: createType(typeName, others),
};
}
function createComponent(docgenInfo: Record<string, any>): Component {
const component = () => {};
// @ts-ignore
component.__docgenInfo = {
[DOCGEN_SECTION]: {
[PROP_NAME]: {
required: false,
...docgenInfo,
},
},
};
return component;
}
TypeSystems.forEach(x => {
it('should map defaults docgen info properly', () => {
const component = createComponent({
...createStringType(x),
description: 'Hey! Hey!',
defaultValue: {
value: 'Default',
},
});
const { propDef } = extractPropsFromDocgen(component, DOCGEN_SECTION)[0];
expect(propDef.name).toBe(PROP_NAME);
expect(propDef.type.summary).toBe('string');
expect(propDef.description).toBe('Hey! Hey!');
expect(propDef.required).toBe(false);
expect(propDef.defaultValue.summary).toBe('Default');
});
it('should remove JSDoc tags from the description', () => {
const component = createComponent({
...createStringType(x),
description: 'Hey!\n@param event\nreturns {string}',
});
const { propDef } = extractPropsFromDocgen(component, DOCGEN_SECTION)[0];
expect(propDef.description).toBe('Hey!');
});
it('should not remove newline characters of multilines description without JSDoc tags', () => {
const component = createComponent({
...createStringType(x),
description: 'onClick description\nis a\nmulti-lines\ndescription',
});
const { propDef } = extractPropsFromDocgen(component, DOCGEN_SECTION)[0];
expect(propDef.description).toBe('onClick description\nis a\nmulti-lines\ndescription');
});
it('should not remove newline characters of multilines description with JSDoc tags', () => {
const component = createComponent({
...createFuncType(x),
description: 'onClick description\nis a\nmulti-lines\ndescription\n@param event',
});
const { propDef } = extractPropsFromDocgen(component, DOCGEN_SECTION)[0];
expect(propDef.description).toBe('onClick description\nis a\nmulti-lines\ndescription');
});
it('should not remove markdown from description without JSDoc tags', () => {
const component = createComponent({
...createStringType(x),
description: 'onClick *emphasis*, **strong**, `formatted` description.',
});
const { propDef } = extractPropsFromDocgen(component, DOCGEN_SECTION)[0];
expect(propDef.description).toBe('onClick *emphasis*, **strong**, `formatted` description.');
});
it('should not remove markdown from description with JSDoc tags', () => {
const component = createComponent({
...createFuncType(x),
description: 'onClick *emphasis*, **strong**, `formatted` description.\n@param event',
});
const { propDef } = extractPropsFromDocgen(component, DOCGEN_SECTION)[0];
expect(propDef.description).toBe('onClick *emphasis*, **strong**, `formatted` description.');
});
it('should return null when the property is marked with @ignore', () => {
const component = createComponent({
...createStringType(x),
description: 'onClick description\n@ignore',
});
expect(extractPropsFromDocgen(component, DOCGEN_SECTION).length).toBe(0);
});
it('should provide raw @param tags', () => {
const component = createComponent({
...createFuncType(x),
description:
'onClick description\n@param {SyntheticEvent} event - Original event.\n@param {string} value',
});
const { propDef } = extractPropsFromDocgen(component, DOCGEN_SECTION)[0];
expect(propDef.description).toBe('onClick description');
expect(propDef.jsDocTags).toBeDefined();
expect(propDef.jsDocTags.params).toBeDefined();
expect(propDef.jsDocTags.params[0].name).toBe('event');
expect(propDef.jsDocTags.params[0].description).toBe('Original event.');
expect(propDef.jsDocTags.params[1].name).toBe('value');
expect(propDef.jsDocTags.params[1].description).toBeNull();
});
});

View File

@ -0,0 +1,77 @@
import { isNil } from 'lodash';
import { PropDef } from '@storybook/components';
import { Component } from '../../blocks/shared';
import { ExtractedJsDoc, parseJsDoc } from '../jsdocParser';
import { DocgenInfo, TypeSystem } from './types';
import { getDocgenSection, isValidDocgenSection } from './utils';
import { getPropDefFactory, PropDefFactory } from './createPropDef';
export interface ExtractedProp {
propDef: PropDef;
docgenInfo: DocgenInfo;
jsDocTags: ExtractedJsDoc;
typeSystem: TypeSystem;
}
export type ExtractProps = (component: Component, section: string) => ExtractedProp[];
const getTypeSystem = (docgenInfo: DocgenInfo): TypeSystem => {
if (!isNil(docgenInfo.type)) {
return TypeSystem.JAVASCRIPT;
}
if (!isNil(docgenInfo.flowType)) {
return TypeSystem.FLOW;
}
if (!isNil(docgenInfo.tsType)) {
return TypeSystem.TYPESCRIPT;
}
return TypeSystem.UNKNOWN;
};
export const extractPropsFromDocgen: ExtractProps = (component, section) => {
const docgenSection = getDocgenSection(component, section);
if (!isValidDocgenSection(docgenSection)) {
return [];
}
const docgenPropsKeys = Object.keys(docgenSection);
const typeSystem = getTypeSystem(docgenSection[docgenPropsKeys[0]]);
const createPropDef = getPropDefFactory(typeSystem);
return docgenPropsKeys
.map(propName => {
const docgenInfo = docgenSection[propName];
return !isNil(docgenInfo)
? extractProp(propName, docgenInfo, typeSystem, createPropDef)
: null;
})
.filter(x => x);
};
function extractProp(
propName: string,
docgenInfo: DocgenInfo,
typeSystem: TypeSystem,
createPropDef: PropDefFactory
): ExtractedProp {
const jsDocParsingResult = parseJsDoc(docgenInfo.description);
const isIgnored = jsDocParsingResult.includesJsDoc && jsDocParsingResult.ignore;
if (!isIgnored) {
const propDef = createPropDef(propName, docgenInfo, jsDocParsingResult);
return {
propDef,
jsDocTags: jsDocParsingResult.extractedTags,
docgenInfo,
typeSystem,
};
}
return null;
}

View File

@ -0,0 +1,3 @@
export * from './types';
export * from './utils';
export * from './extractDocgenProps';

View File

@ -0,0 +1,44 @@
import { PropsTableProps } from '@storybook/components';
import { Component } from '../../blocks/shared';
export type PropsExtractor = (component: Component) => PropsTableProps | null;
export interface DocgenBaseType {
name: string;
description?: string;
require?: boolean;
}
export interface DocgenPropType extends DocgenBaseType {
value?: any;
raw?: string;
computed?: boolean;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface DocgenFlowType extends DocgenBaseType {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface DocgenTypeScriptType extends DocgenBaseType {}
export type DocgenType = DocgenPropType | DocgenFlowType | DocgenTypeScriptType;
export interface DocgenPropDefaultValue {
value: string;
}
export interface DocgenInfo {
type?: DocgenPropType;
flowType?: DocgenFlowType;
tsType?: DocgenTypeScriptType;
required: boolean;
description?: string;
defaultValue?: DocgenPropDefaultValue;
}
export enum TypeSystem {
JAVASCRIPT = 'JavaScript',
FLOW = 'Flow',
TYPESCRIPT = 'TypeScript',
UNKNOWN = 'Unknown',
}

View File

@ -0,0 +1,29 @@
/* eslint-disable no-underscore-dangle */
import { isNil } from 'lodash';
import { Component } from '../../blocks/shared';
export const str = (obj: any) => {
if (!obj) {
return '';
}
if (typeof obj === 'string') {
return obj as string;
}
throw new Error(`Description: expected string, got: ${JSON.stringify(obj)}`);
};
export function hasDocgen(component: Component): boolean {
return !!component.__docgenInfo;
}
export function isValidDocgenSection(docgenSection: any) {
return !isNil(docgenSection) && Object.keys(docgenSection).length > 0;
}
export function getDocgenSection(component: Component, section: string): any {
return hasDocgen(component) ? component.__docgenInfo[section] : null;
}
export const extractComponentDescription = (component?: Component) =>
component && hasDocgen(component) && str(component.__docgenInfo.description);

View File

@ -1,55 +0,0 @@
/* eslint-disable no-underscore-dangle */
import { PropDef, PropsTableProps } from '@storybook/components';
import { Component } from '../blocks/shared';
import { getTypeSystemHandler, getPropTypeSystem } from './type-system-handlers';
export type PropsExtractor = (component: Component) => PropsTableProps | null;
export type PropDefGetter = (component: Component, section: string) => PropDef[];
export const str = (o: any) => {
if (!o) {
return '';
}
if (typeof o === 'string') {
return o as string;
}
throw new Error(`Description: expected string, got: ${JSON.stringify(o)}`);
};
export const hasDocgen = (component: Component) => !!component.__docgenInfo;
export const hasDocgenSection = (component: Component, section: string) =>
component &&
component.__docgenInfo &&
component.__docgenInfo[section] &&
Object.keys(component.__docgenInfo[section]).length > 0;
export const extractPropsFromDocgen: PropDefGetter = (component, section) => {
if (!hasDocgenSection(component, section)) {
return [];
}
const props: Record<string, PropDef> = {};
const docgenInfoProps = component.__docgenInfo[section];
const propKeys = Object.keys(docgenInfoProps);
// Assuming the props for a given component will all have the same type system.
const typeSystem = getPropTypeSystem(docgenInfoProps[propKeys[0]]);
const typeSystemHandler = getTypeSystemHandler(typeSystem);
propKeys.forEach(propKey => {
const docgenInfoProp = docgenInfoProps[propKey];
const result = typeSystemHandler(propKey, docgenInfoProp);
if (!result.ignore) {
props[propKey] = result.propDef;
}
});
return Object.values(props);
};
export const extractComponentDescription = (component?: Component) =>
component && component.__docgenInfo && str(component.__docgenInfo.description);

View File

@ -1,187 +0,0 @@
import doctrine, { Annotation } from 'doctrine';
import { isNil } from 'lodash';
import { DocgenInfo } from './DocgenInfo';
export type ParseJsDoc = (docgenInfo: DocgenInfo) => JsDocParsingResult;
export interface JsDocParsingResult {
ignore: boolean;
description?: string;
extractedTags?: ExtractedJsDocTags;
}
export interface ExtractedJsDocParamTag {
name: string;
type?: doctrine.Type;
description?: string;
raw: doctrine.Tag;
getPrettyName: () => string;
getTypeName: () => string;
}
export interface ExtractedJsDocReturnsTag {
type?: doctrine.Type;
description?: string;
raw: doctrine.Tag;
getTypeName: () => string;
}
export interface ExtractedJsDocTags {
params?: ExtractedJsDocParamTag[];
returns?: ExtractedJsDocReturnsTag;
ignore: boolean;
}
function parse(content: string): Annotation {
let ast;
try {
ast = doctrine.parse(content, {
tags: ['param', 'arg', 'argument', 'returns', 'ignore'],
sloppy: true,
});
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
throw new Error('Cannot parse JSDoc tags.');
}
return ast;
}
export const parseJsDoc: ParseJsDoc = (docgenInfo: DocgenInfo) => {
const jsDocAst = parse(docgenInfo.description);
const extractedTags = extractJsDocTags(jsDocAst);
if (extractedTags.ignore) {
// There is no point in doing other stuff since this prop will not be rendered.
return {
ignore: true,
};
}
return {
ignore: false,
// Always use the parsed description to ensure JSDoc is removed from the description.
description: jsDocAst.description,
extractedTags,
};
};
function extractJsDocTags(ast: doctrine.Annotation): ExtractedJsDocTags {
const extractedTags: ExtractedJsDocTags = {
params: null,
returns: null,
ignore: false,
};
for (let i = 0; i < ast.tags.length; i += 1) {
const tag = ast.tags[i];
// arg & argument are aliases for param.
if (tag.title === 'param' || tag.title === 'arg' || tag.title === 'argument') {
const paramName = tag.name;
// When the @param doesn't have a name but have a type and a description, "null-null" is returned.
if (!isNil(paramName) && paramName !== 'null-null') {
if (isNil(extractedTags.params)) {
extractedTags.params = [];
}
extractedTags.params.push({
name: tag.name,
type: tag.type,
description: tag.description,
raw: tag,
getPrettyName: () => {
if (paramName.includes('null')) {
// There is a few cases in which the returned param name contains "null".
// - @param {SyntheticEvent} event- Original SyntheticEvent
// - @param {SyntheticEvent} event.\n@returns {string}
return paramName.replace('-null', '').replace('.null', '');
}
return tag.name;
},
getTypeName: () => {
return !isNil(tag.type) ? extractJsDocTypeName(tag.type) : null;
},
});
}
} else if (tag.title === 'returns') {
if (!isNil(tag.type)) {
extractedTags.returns = {
type: tag.type,
description: tag.description,
raw: tag,
getTypeName: () => {
return extractJsDocTypeName(tag.type);
},
};
}
} else if (tag.title === 'ignore') {
extractedTags.ignore = true;
// Once we reach an @ignore tag, there is no point in parsing the other tags since we will not render the prop.
break;
}
}
return extractedTags;
}
// FIXME: type argument should be doctrine.Type instead of any.
function extractJsDocTypeName(type: any): string {
if (type.type === 'NameExpression') {
return type.name;
}
if (type.type === 'RecordType') {
const recordFields = type.fields.map((field: doctrine.type.FieldType) => {
if (!isNil(field.value)) {
const valueTypeName = extractJsDocTypeName(field.value);
return `${field.key}: ${valueTypeName}`;
}
return field.key;
});
return `({${recordFields.join(', ')}})`;
}
if (type.type === 'UnionType') {
const unionElements = type.elements.map(extractJsDocTypeName);
return `(${unionElements.join('|')})`;
}
// Only support untyped array: []. Might add more support later if required.
if (type.type === 'ArrayType') {
return '[]';
}
if (type.type === 'TypeApplication') {
if (!isNil(type.expression)) {
if (type.expression.name === 'Array') {
const arrayType = extractJsDocTypeName(type.applications[0]);
return `${arrayType}[]`;
}
}
}
if (
type.type === 'NullableType' ||
type.type === 'NonNullableType' ||
type.type === 'OptionalType'
) {
return extractJsDocTypeName(type.expression);
}
if (type.type === 'AllLiteral') {
return 'any';
}
return null;
}

View File

@ -0,0 +1,344 @@
import { parseJsDoc } from './jsdocParser';
describe('parseJsDoc', () => {
it('should set includesJsDoc to false when the value is null', () => {
const { includesJsDoc, description, extractedTags } = parseJsDoc(null);
expect(includesJsDoc).toBeFalsy();
expect(description).toBeUndefined();
expect(extractedTags).toBeUndefined();
});
it('should set includesJsDocto to false when the value dont contains JSDoc', () => {
const { includesJsDoc, description, extractedTags } = parseJsDoc('Hey!');
expect(includesJsDoc).toBeFalsy();
expect(description).toBeUndefined();
expect(extractedTags).toBeUndefined();
});
it('should set includesJsDoc to true when the value contains JSDoc', () => {
const { includesJsDoc } = parseJsDoc('Hey!\n@version 1.2');
expect(includesJsDoc).toBeTruthy();
});
it('should remove all JSDoc tags from the description', () => {
const { description } = parseJsDoc('Hey!\n@version 1.2\n@deprecated');
expect(description).toBe('Hey!');
});
describe('@ignore', () => {
it('should set ignore to true when @ignore is present', () => {
const { ignore, description, extractedTags } = parseJsDoc('Hey!\n@ignore');
expect(ignore).toBeTruthy();
expect(description).toBeUndefined();
expect(extractedTags).toBeUndefined();
});
it('should set ignore to false when @ignore is not present', () => {
const { ignore } = parseJsDoc('Hey!\n@version 1.2');
expect(ignore).toBeFalsy();
});
});
describe('@param', () => {
it('should ignore invalid @param tags', () => {
const { extractedTags } = parseJsDoc('@param');
expect(extractedTags.params).toBeNull();
});
it('should return a @param with a name', () => {
const { extractedTags } = parseJsDoc('@param event');
expect(extractedTags.params).not.toBeNull();
expect(extractedTags.params[0].name).toBe('event');
expect(extractedTags.params[0].type).toBeNull();
expect(extractedTags.params[0].description).toBeNull();
});
it('should return a @param with a name and a type', () => {
const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event');
expect(extractedTags.params).not.toBeNull();
expect(extractedTags.params[0].name).toBe('event');
expect(extractedTags.params[0].type).not.toBeNull();
expect(extractedTags.params[0].type.name).toBe('SyntheticEvent');
expect(extractedTags.params[0].description).toBeNull();
});
it('should return a @param with a name, a type and a description', () => {
const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event - React event');
expect(extractedTags.params).not.toBeNull();
expect(extractedTags.params[0].name).toBe('event');
expect(extractedTags.params[0].type).not.toBeNull();
expect(extractedTags.params[0].type.name).toBe('SyntheticEvent');
expect(extractedTags.params[0].description).toBe('React event');
});
it('should support multiple @param tags', () => {
const { extractedTags } = parseJsDoc(
'@param {SyntheticEvent} event1 - React event\n@param {SyntheticEvent} event2 - React event\n@param {SyntheticEvent} event3 - React event'
);
['event1', 'event2', 'event3'].forEach((x, i) => {
expect(extractedTags.params[i].name).toBe(x);
expect(extractedTags.params[i].type).not.toBeNull();
expect(extractedTags.params[i].type.name).toBe('SyntheticEvent');
expect(extractedTags.params[i].description).toBe('React event');
});
});
it('should not return extra @param', () => {
const { extractedTags } = parseJsDoc('@param event');
expect(Object.keys(extractedTags.params).length).toBe(1);
});
it('should support multiline description when there is a @param', () => {
const { description, extractedTags } = parseJsDoc(
'This is a\nmultiline description\n@param event'
);
expect(description).toBe('This is a\nmultiline description');
expect(extractedTags.params).not.toBeNull();
expect(extractedTags.params[0].name).toBe('event');
});
it('should support multiline @param description', () => {
const { extractedTags } = parseJsDoc(
'@param event - This is a\nmultiline description\n@param anotherEvent'
);
expect(extractedTags.params).not.toBeNull();
expect(extractedTags.params[0].name).toBe('event');
expect(extractedTags.params[0].description).toBe('This is a\nmultiline description');
expect(extractedTags.params[1].name).toBe('anotherEvent');
});
['@arg', '@argument'].forEach(x => {
it(`should support ${x} alias`, () => {
const { extractedTags } = parseJsDoc(`${x} {SyntheticEvent} event - React event`);
expect(extractedTags.params).not.toBeNull();
expect(extractedTags.params[0].name).toBe('event');
expect(extractedTags.params[0].type).not.toBeNull();
expect(extractedTags.params[0].type.name).toBe('SyntheticEvent');
expect(extractedTags.params[0].description).toBe('React event');
});
});
describe('getTypeName', () => {
it('should support record type with a single field', () => {
const { extractedTags } = parseJsDoc('@param {{a: number}} event');
expect(extractedTags.params[0].getTypeName()).toBe('({a: number})');
});
it('should support record type with multiple fields', () => {
const { extractedTags } = parseJsDoc('@param {{a: number, b: string}} event');
expect(extractedTags.params[0].getTypeName()).toBe('({a: number, b: string})');
});
it('should support record type with a field having only a name', () => {
const { extractedTags } = parseJsDoc('@param {{a}} event');
expect(extractedTags.params[0].getTypeName()).toBe('({a})');
});
it('should support union type', () => {
const { extractedTags } = parseJsDoc('@param {(number|boolean)} event');
expect(extractedTags.params[0].getTypeName()).toBe('(number|boolean)');
});
it('should support array type', () => {
const { extractedTags } = parseJsDoc('@param {number[]} event');
expect(extractedTags.params[0].getTypeName()).toBe('number[]');
});
it('should support untyped array type', () => {
const { extractedTags } = parseJsDoc('@param {[]} event');
expect(extractedTags.params[0].getTypeName()).toBe('[]');
});
it('should support nullable type', () => {
const { extractedTags } = parseJsDoc('@param {?number} event');
expect(extractedTags.params[0].getTypeName()).toBe('number');
});
it('should support non nullable type', () => {
const { extractedTags } = parseJsDoc('@param {!number} event');
expect(extractedTags.params[0].getTypeName()).toBe('number');
});
it('should support optional param with []', () => {
const { extractedTags } = parseJsDoc('@param {number} [event]');
expect(extractedTags.params[0].getTypeName()).toBe('number');
});
it('should support optional param with =', () => {
const { extractedTags } = parseJsDoc('@param {number=} event');
expect(extractedTags.params[0].getTypeName()).toBe('number');
});
it('should support any type', () => {
const { extractedTags } = parseJsDoc('@param {*} event');
expect(extractedTags.params[0].getTypeName()).toBe('any');
});
});
describe('getPrettyName', () => {
it('should return @param name', () => {
const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event - React event');
expect(extractedTags.params[0].getPrettyName()).toBe('event');
});
it('should fix missing space between the @param name and the description separator', () => {
const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event- React event');
expect(extractedTags.params[0].getPrettyName()).toBe('event');
});
it('should fix @param name ending with . followed by a @returns tag', () => {
const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event.\n');
expect(extractedTags.params[0].getPrettyName()).toBe('event');
});
});
});
describe('@returns', () => {
it('should ignore invalid @returns', () => {
const { extractedTags } = parseJsDoc('@returns');
expect(extractedTags.returns).toBeNull();
});
it('should return a @returns with a type', () => {
const { extractedTags } = parseJsDoc('@returns {string}');
expect(extractedTags.returns).not.toBeNull();
expect(extractedTags.returns.type).not.toBeNull();
expect(extractedTags.returns.type.name).toBe('string');
});
it('should return a @returns with a type and a description', () => {
const { extractedTags } = parseJsDoc('@returns {string} - A bar description');
expect(extractedTags.returns).not.toBeNull();
expect(extractedTags.returns.type).not.toBeNull();
expect(extractedTags.returns.type.name).toBe('string');
expect(extractedTags.returns.description).toBe('A bar description');
});
it('should support multiline @returns description', () => {
const { extractedTags } = parseJsDoc(
'@returns {string} - This is\na multiline\ndescription\n'
);
expect(extractedTags.returns).not.toBeNull();
expect(extractedTags.returns.type).not.toBeNull();
expect(extractedTags.returns.type.name).toBe('string');
expect(extractedTags.returns.description).toBe('This is\na multiline\ndescription');
});
it('should only consider the last @returns tag when there is multiple', () => {
const { extractedTags } = parseJsDoc('@returns {string}\n@returns {number}');
expect(extractedTags.returns).not.toBeNull();
expect(extractedTags.returns.type).not.toBeNull();
expect(extractedTags.returns.type.name).toBe('number');
});
describe('getTypeName', () => {
it('should support named type', () => {
const { extractedTags } = parseJsDoc('@returns {string}');
expect(extractedTags.returns.getTypeName()).toBe('string');
});
it('should support record type with a single field', () => {
const { extractedTags } = parseJsDoc('@returns {{a: number}}');
expect(extractedTags.returns.getTypeName()).toBe('({a: number})');
});
it('should support record type with multiple fields', () => {
const { extractedTags } = parseJsDoc('@returns {{a: number, b: string}}');
expect(extractedTags.returns.getTypeName()).toBe('({a: number, b: string})');
});
it('should support record type with a field having only a name', () => {
const { extractedTags } = parseJsDoc('@returns {{a}}');
expect(extractedTags.returns.getTypeName()).toBe('({a})');
});
it('should support array type', () => {
const { extractedTags } = parseJsDoc('@returns {integer[]}');
expect(extractedTags.returns.getTypeName()).toBe('integer[]');
});
it('should support untyped array type', () => {
const { extractedTags } = parseJsDoc('@returns {[]}');
expect(extractedTags.returns.getTypeName()).toBe('[]');
});
it('should support union type', () => {
const { extractedTags } = parseJsDoc('@returns {(number|boolean)}');
expect(extractedTags.returns.getTypeName()).toBe('(number|boolean)');
});
it('should support any type', () => {
const { extractedTags } = parseJsDoc('@returns {*}');
expect(extractedTags.returns.getTypeName()).toBe('any');
});
it('should support void', () => {
const { extractedTags } = parseJsDoc('@returns {void}');
expect(extractedTags.returns.getTypeName()).toBe('void');
});
});
});
it('should ignore unsupported JSDoc tags', () => {
const { extractedTags } = parseJsDoc('Hey!\n@param event', { tags: [] });
expect(extractedTags.params).toBeNull();
});
it('should remove extra newline characters between tags', () => {
const { extractedTags } = parseJsDoc(
'Hey!\n@param {SyntheticEvent} event - Original event.\n \n \n \n@returns {string}'
);
expect(extractedTags.params).not.toBeNull();
expect(Object.keys(extractedTags.params).length).toBe(1);
expect(extractedTags.params[0].name).toBe('event');
expect(extractedTags.params[0].type.name).toBe('SyntheticEvent');
expect(extractedTags.params[0].description).toBe('Original event.');
expect(extractedTags.returns).not.toBeNull();
expect(extractedTags.returns.type.name).toBe('string');
});
});

View File

@ -0,0 +1,234 @@
import doctrine, { Annotation } from 'doctrine';
import { isNil } from 'lodash';
export interface ExtractedJsDocParam {
name: string;
type?: any;
description?: string;
getPrettyName: () => string;
getTypeName: () => string;
}
export interface ExtractedJsDocReturns {
type?: any;
description?: string;
getTypeName: () => string;
}
export interface ExtractedJsDoc {
params?: ExtractedJsDocParam[];
returns?: ExtractedJsDocReturns;
ignore: boolean;
}
export interface JsDocParsingOptions {
tags?: string[];
}
export interface JsDocParsingResult {
includesJsDoc: boolean;
ignore: boolean;
description?: string;
extractedTags?: ExtractedJsDoc;
}
export type ParseJsDoc = (value?: string, options?: JsDocParsingOptions) => JsDocParsingResult;
function containsJsDoc(value?: string): boolean {
return !isNil(value) && value.includes('@');
}
function parse(content: string, tags: string[]): Annotation {
let ast;
try {
ast = doctrine.parse(content, {
tags,
sloppy: true,
});
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
throw new Error('Cannot parse JSDoc tags.');
}
return ast;
}
const DEFAULT_OPTIONS = {
tags: ['param', 'arg', 'argument', 'returns', 'ignore'],
};
export const parseJsDoc: ParseJsDoc = (
value?: string,
options: JsDocParsingOptions = DEFAULT_OPTIONS
) => {
if (!containsJsDoc(value)) {
return {
includesJsDoc: false,
ignore: false,
};
}
const jsDocAst = parse(value, options.tags);
const extractedTags = extractJsDocTags(jsDocAst);
if (extractedTags.ignore) {
// There is no point in doing other stuff since this prop will not be rendered.
return {
includesJsDoc: true,
ignore: true,
};
}
return {
includesJsDoc: true,
ignore: false,
// Always use the parsed description to ensure JSDoc is removed from the description.
description: jsDocAst.description,
extractedTags,
};
};
function extractJsDocTags(ast: doctrine.Annotation): ExtractedJsDoc {
const extractedTags: ExtractedJsDoc = {
params: null,
returns: null,
ignore: false,
};
for (let i = 0; i < ast.tags.length; i += 1) {
const tag = ast.tags[i];
if (tag.title === 'ignore') {
extractedTags.ignore = true;
// Once we reach an @ignore tag, there is no point in parsing the other tags since we will not render the prop.
break;
} else {
switch (tag.title) {
// arg & argument are aliases for param.
case 'param':
case 'arg':
case 'argument': {
const paramTag = extractParam(tag);
if (!isNil(paramTag)) {
if (isNil(extractedTags.params)) {
extractedTags.params = [];
}
extractedTags.params.push(paramTag);
}
break;
}
case 'returns': {
const returnsTag = extractReturns(tag);
if (!isNil(returnsTag)) {
extractedTags.returns = returnsTag;
}
break;
}
default:
break;
}
}
}
return extractedTags;
}
function extractParam(tag: doctrine.Tag): ExtractedJsDocParam {
const paramName = tag.name;
// When the @param doesn't have a name but have a type and a description, "null-null" is returned.
if (!isNil(paramName) && paramName !== 'null-null') {
return {
name: tag.name,
type: tag.type,
description: tag.description,
getPrettyName: () => {
if (paramName.includes('null')) {
// There is a few cases in which the returned param name contains "null".
// - @param {SyntheticEvent} event- Original SyntheticEvent
// - @param {SyntheticEvent} event.\n@returns {string}
return paramName.replace('-null', '').replace('.null', '');
}
return tag.name;
},
getTypeName: () => {
return !isNil(tag.type) ? extractTypeName(tag.type) : null;
},
};
}
return null;
}
function extractReturns(tag: doctrine.Tag): ExtractedJsDocReturns {
if (!isNil(tag.type)) {
return {
type: tag.type,
description: tag.description,
getTypeName: () => {
return extractTypeName(tag.type);
},
};
}
return null;
}
function extractTypeName(type: doctrine.Type): string {
if (type.type === 'NameExpression') {
return type.name;
}
if (type.type === 'RecordType') {
const recordFields = type.fields.map((field: doctrine.type.FieldType) => {
if (!isNil(field.value)) {
const valueTypeName = extractTypeName(field.value);
return `${field.key}: ${valueTypeName}`;
}
return field.key;
});
return `({${recordFields.join(', ')}})`;
}
if (type.type === 'UnionType') {
const unionElements = type.elements.map(extractTypeName);
return `(${unionElements.join('|')})`;
}
// Only support untyped array: []. Might add more support later if required.
if (type.type === 'ArrayType') {
return '[]';
}
if (type.type === 'TypeApplication') {
if (!isNil(type.expression)) {
if ((type.expression as doctrine.type.NameExpression).name === 'Array') {
const arrayType = extractTypeName(type.applications[0]);
return `${arrayType}[]`;
}
}
}
if (
type.type === 'NullableType' ||
type.type === 'NonNullableType' ||
type.type === 'OptionalType'
) {
return extractTypeName(type.expression);
}
if (type.type === 'AllLiteral') {
return 'any';
}
return null;
}

View File

@ -1,903 +0,0 @@
import { propTypesHandler, tsHandler, flowHandler, unknownHandler } from './type-system-handlers';
import { DocgenInfo } from './DocgenInfo';
const DEFAULT_PROP_NAME = 'propName';
const PROP_TYPES_STRING_TYPE = {
name: 'string',
};
const PROP_TYPES_FUNC_TYPE = {
name: 'func',
};
const TS_FUNC_TYPE = {
name: '() => void',
};
const TS_STRING_TYPE = {
name: 'string',
};
function createDocgenInfo(overrides: Record<string, any> = {}): DocgenInfo {
return {
type: null,
required: true,
description: 'A string prop',
defaultValue: {
value: 'default string',
},
...overrides,
};
}
describe('prop-types handler', () => {
it('should map defaults docgen info properly', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_STRING_TYPE,
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.name).toBe(DEFAULT_PROP_NAME);
expect(propDef.type.name).toBe(docgenInfo.type.name);
expect(propDef.description).toBe(docgenInfo.description);
expect(propDef.required).toBe(docgenInfo.required);
expect(propDef.defaultValue).toBe(docgenInfo.defaultValue.value);
});
describe('for all prop types', () => {
it('should handle prop without a description', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_STRING_TYPE,
description: undefined,
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBeUndefined();
});
it('should clean the description', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_STRING_TYPE,
description: 'onClick description\n@param {SyntheticEvent} event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBe('onClick description');
});
it('should have an empty description when the description only contains JSDoc', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_STRING_TYPE,
description: '@param event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBe('');
});
it('should not remove newline characters of multilines description without JSDoc tags', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_STRING_TYPE,
description: 'onClick description\nis a\nmulti-lines\ndescription',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBe('onClick description\nis a\nmulti-lines\ndescription');
});
it('should not remove newline characters of multilines description with JSDoc tags', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_STRING_TYPE,
description: 'onClick description\nis a\nmulti-lines\ndescription\n@param event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBe('onClick description\nis a\nmulti-lines\ndescription');
});
it('should not remove markdown from description without JSDoc tags', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_STRING_TYPE,
description: 'onClick *emphasis*, **strong**, `formatted` description.',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBe('onClick *emphasis*, **strong**, `formatted` description.');
});
it('should not remove markdown from description with JSDoc tags', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_STRING_TYPE,
description: 'onClick *emphasis*, **strong**, `formatted` description.\n@param event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBe('onClick *emphasis*, **strong**, `formatted` description.');
});
it('should not remove @ characters that does not match JSDoc tags', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_STRING_TYPE,
description: 'onClick @description@',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBe('onClick @description@');
});
it('should not set ignore to true when the property is not marked with @ignore', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_STRING_TYPE,
description: 'onClick description',
});
const { ignore } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(ignore).toBeFalsy();
});
it('should set ignore to true when the property is marked with @ignore', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_STRING_TYPE,
description: 'onClick description\n@ignore',
});
const { ignore } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(ignore).toBeTruthy();
});
});
describe('when the prop is a function', () => {
it("should have func as type when the props doesn't have a description", () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: undefined,
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBeUndefined();
expect(propDef.type.name).toBe('func');
});
it('should have func as type when the prop have a description without JSDoc', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('func');
expect(propDef.description).toBe('onClick description');
});
it('should have an empty description when the description only contains JSDoc', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: '@param event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBe('');
});
describe('when the description contains a @param tag', () => {
it('should have func as type when it is an invalid @param tag', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('func');
expect(propDef.description).toBe('onClick description');
});
it('should have a func signature with a single arg as type when it is a @param tag with a name', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event)');
expect(propDef.description).toBe('onClick description');
});
it('should have a func signature with a single arg as type when it is a @param tag with a name and a type', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param {SyntheticEvent} event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: SyntheticEvent)');
expect(propDef.description).toBe('onClick description');
});
it('should have a func signature with a single arg as type when it is a @param tag with a name, a type and a desc', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description:
'onClick description\n@param {SyntheticEvent} event - Original SyntheticEvent',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: SyntheticEvent)');
expect(propDef.description).toBe('onClick description');
});
it('should have func as type when it is @param tag without a name 1', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param - Original SyntheticEvent',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('func');
expect(propDef.description).toBe('onClick description');
});
it('should have func as type when it is @param tag without a name 2', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param {SyntheticEvent} - Original SyntheticEvent',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('func');
expect(propDef.description).toBe('onClick description');
});
it('should support param of record type with a single field', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param {{a: number}} event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: ({a: number}))');
expect(propDef.description).toBe('onClick description');
});
it('should support param of record type with multiple fields', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param {{a: number, b: string}} event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: ({a: number, b: string}))');
expect(propDef.description).toBe('onClick description');
});
it('should support param of record type with a field having only a name', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param {{a}} event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: ({a}))');
expect(propDef.description).toBe('onClick description');
});
it('should support param of union type', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param {(number|boolean)} event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: (number|boolean))');
expect(propDef.description).toBe('onClick description');
});
it('should support param of array type', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param {number[]} event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: number[])');
expect(propDef.description).toBe('onClick description');
});
it('should support param of untyped array type', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param {[]} event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: [])');
expect(propDef.description).toBe('onClick description');
});
it('should support param with a nullable type', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param {?number} event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: number)');
expect(propDef.description).toBe('onClick description');
});
it('should support param with a non nullable type', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param {!number} event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: number)');
expect(propDef.description).toBe('onClick description');
});
it('should support optional param 1', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param {number} [event]',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: number)');
expect(propDef.description).toBe('onClick description');
});
it('should support optional param 2', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param {number=} event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: number)');
expect(propDef.description).toBe('onClick description');
});
it('should support param of type any', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param {*} event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: any)');
expect(propDef.description).toBe('onClick description');
});
it('should support multilines description when there is a @param', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\nis a\nmulti-lines\ndescription\n@param event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event)');
expect(propDef.description).toBe('onClick description\nis a\nmulti-lines\ndescription');
});
it('should support multilines @param description', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description:
'onClick description\n@param event - This is my param\nmultiline description\n@param customData',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event, customData)');
expect(propDef.description).toBe('onClick description');
});
it('should autofix missing space between the param name and the description separator', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description:
'onClick description\n@param {SyntheticEvent} event- Original SyntheticEvent',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: SyntheticEvent)');
expect(propDef.description).toBe('onClick description');
});
it('should autofix param name ending with . followed by a @returns tag', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param {SyntheticEvent} event.\n',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: SyntheticEvent)');
expect(propDef.description).toBe('onClick description');
});
it('should provide raw @param tags', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description:
'onClick description\n@param {SyntheticEvent} event - Original event.\n@param {string} value',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBe('onClick description');
expect(propDef.jsDocTags).toBeDefined();
expect(propDef.jsDocTags.params).toBeDefined();
expect(propDef.jsDocTags.params[0].name).toBe('event');
expect(propDef.jsDocTags.params[0].description).toBe('Original event.');
expect(propDef.jsDocTags.params[1].name).toBe('value');
expect(propDef.jsDocTags.params[1].description).toBeNull();
});
});
describe('when the description contains multiple @param tags', () => {
it('should have a func signature with multiple args as type', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description:
'onClick description\n@param {SyntheticEvent} event\n@param {string} customData',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: SyntheticEvent, customData: string)');
expect(propDef.description).toBe('onClick description');
});
it('should ignore invalid @param tags', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description:
'onClick description\n@param {SyntheticEvent} event\n@param {string} customData\n@param {SyntheticEvent} - Original event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: SyntheticEvent, customData: string)');
expect(propDef.description).toBe('onClick description');
});
});
it('should support @arg alias', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@arg event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event)');
expect(propDef.description).toBe('onClick description');
});
it('should support @argument alias', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@argument event',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event)');
expect(propDef.description).toBe('onClick description');
});
describe('when the description contains a @returns tag', () => {
it('should have func as type when it is an invalid @returns tag', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@returns',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('func');
expect(propDef.description).toBe('onClick description');
});
it('should have a func signature with a return type as type when it is a @returns tag with a type', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@returns {string}',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('() => string');
expect(propDef.description).toBe('onClick description');
});
it('should have a func signature with a return type as type when it is a @returns tag with a type and a description', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@returns {string} - A custom return type',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('() => string');
expect(propDef.description).toBe('onClick description');
});
it('should have func as type when it is a @returns tag without a type and there is no params.', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@returns - A custom return type',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('func');
expect(propDef.description).toBe('onClick description');
});
it('should have no return type when it is a @returns tag without a type and there is params.', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@param event\n@returns - A custom return type',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event)');
expect(propDef.description).toBe('onClick description');
});
it('should have a full signature as type when there is a @param and a @returns tag 1', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description:
'onClick description\n@param {SyntheticEvent} event - Original event.\n@returns {string}',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: SyntheticEvent) => string');
expect(propDef.description).toBe('onClick description');
});
it('should have a full signature as type when there is a @param and a @returns tag 2', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description:
'onClick description\n@param {SyntheticEvent} event - Original event.\n@param {string} customData\n@returns {string}',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: SyntheticEvent, customData: string) => string');
expect(propDef.description).toBe('onClick description');
});
it('should only consider the last @returns tag when there is more than one', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@returns {string}\n@returns {integer}',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('() => integer');
expect(propDef.description).toBe('onClick description');
});
it('should support returns of record type', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@returns {{a: number, b: string}}',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('() => ({a: number, b: string})');
expect(propDef.description).toBe('onClick description');
});
it('should support returns of array type', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@returns {integer[]}',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('() => integer[]');
expect(propDef.description).toBe('onClick description');
});
it('should support returns of union type', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@returns {(number|boolean)}',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('() => (number|boolean)');
expect(propDef.description).toBe('onClick description');
});
it('should support returns of type any', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@returns {*}',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('() => any');
expect(propDef.description).toBe('onClick description');
});
it('should support returns of type void', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description: 'onClick description\n@returns {void}',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('() => void');
expect(propDef.description).toBe('onClick description');
});
it('should provide raw @returns tags when a description is defined', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description:
'onClick description\n@param {SyntheticEvent} event - Original event.\n@returns {string} - An awesome string.',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBe('onClick description');
expect(propDef.jsDocTags).toBeDefined();
expect(propDef.jsDocTags.returns).toBeDefined();
expect(propDef.jsDocTags.returns.description).toBe('An awesome string.');
});
it('should provide raw @returns tags when there is no description', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description:
'onClick description\n@param {SyntheticEvent} event - Original event.\n@returns {string}',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBe('onClick description');
expect(propDef.jsDocTags).toBeDefined();
expect(propDef.jsDocTags.returns).toBeDefined();
expect(propDef.jsDocTags.returns.description).toBeNull();
});
});
it('should remove extra newline characters between tags', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description:
'onClick description\n@param {SyntheticEvent} event - Original event.\n \n \n \n@returns {string}',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: SyntheticEvent) => string');
expect(propDef.description).toBe('onClick description');
});
it('should ignore unsupported JSDoc tags', () => {
const docgenInfo = createDocgenInfo({
type: PROP_TYPES_FUNC_TYPE,
description:
'onClick description\n@param {SyntheticEvent} event\n@type {number}\n@returns {string}\n@version 2',
});
const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.type.name).toBe('(event: SyntheticEvent) => string');
expect(propDef.description).toBe('onClick description');
});
});
});
describe('ts handler', () => {
it('should map defaults docgen info properly', () => {
const docgenInfo = createDocgenInfo({
tsType: PROP_TYPES_STRING_TYPE,
});
const { propDef } = tsHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.name).toBe(DEFAULT_PROP_NAME);
expect(propDef.type.name).toBe(docgenInfo.tsType.name);
expect(propDef.description).toBe(docgenInfo.description);
expect(propDef.required).toBe(docgenInfo.required);
expect(propDef.defaultValue).toBe(docgenInfo.defaultValue.value);
});
it('should provide raw @param tags', () => {
const docgenInfo = createDocgenInfo({
tsType: TS_FUNC_TYPE,
description:
'onClick description\n@param {SyntheticEvent} event - Original event.\n@param {string} value',
});
const { propDef } = tsHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBe('onClick description');
expect(propDef.jsDocTags).toBeDefined();
expect(propDef.jsDocTags.params).toBeDefined();
expect(propDef.jsDocTags.params[0].name).toBe('event');
expect(propDef.jsDocTags.params[0].description).toBe('Original event.');
expect(propDef.jsDocTags.params[1].name).toBe('value');
expect(propDef.jsDocTags.params[1].description).toBeNull();
});
it('should provide raw @returns tags when a description is defined', () => {
const docgenInfo = createDocgenInfo({
tsType: TS_FUNC_TYPE,
description:
'onClick description\n@param {SyntheticEvent} event - Original event.\n@returns {string} - An awesome string.',
});
const { propDef } = tsHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBe('onClick description');
expect(propDef.jsDocTags).toBeDefined();
expect(propDef.jsDocTags.returns).toBeDefined();
expect(propDef.jsDocTags.returns.description).toBe('An awesome string.');
});
it('should provide raw @returns tags when there is no description', () => {
const docgenInfo = createDocgenInfo({
tsType: TS_FUNC_TYPE,
description:
'onClick description\n@param {SyntheticEvent} event - Original event.\n@returns {string}',
});
const { propDef } = tsHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.description).toBe('onClick description');
expect(propDef.jsDocTags).toBeDefined();
expect(propDef.jsDocTags.returns).toBeDefined();
expect(propDef.jsDocTags.returns.description).toBeNull();
});
it('should not set ignore to true when the property is not marked with @ignore', () => {
const docgenInfo = createDocgenInfo({
tsType: TS_STRING_TYPE,
description: 'onClick description',
});
const { ignore } = tsHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(ignore).toBeFalsy();
});
it('should set ignore to true when the property is marked with @ignore', () => {
const docgenInfo = createDocgenInfo({
tsType: TS_STRING_TYPE,
description: 'onClick description\n@ignore',
});
const { ignore } = tsHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(ignore).toBeTruthy();
});
});
describe('flow handler', () => {
it('should map defaults docgen info properly', () => {
const docgenInfo = createDocgenInfo({
flowType: {
name: 'string',
},
});
const { propDef } = flowHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.name).toBe(DEFAULT_PROP_NAME);
expect(propDef.type.name).toBe(docgenInfo.flowType.name);
expect(propDef.description).toBe(docgenInfo.description);
expect(propDef.required).toBe(docgenInfo.required);
expect(propDef.defaultValue).toBe(docgenInfo.defaultValue.value);
});
it('should not set ignore to true when the property is not marked with @ignore', () => {
const docgenInfo = createDocgenInfo({
flowType: {
name: 'string',
},
description: 'onClick description',
});
const { ignore } = flowHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(ignore).toBeFalsy();
});
it('should set ignore to true when the property is marked with @ignore', () => {
const docgenInfo = createDocgenInfo({
flowType: {
name: 'string',
},
description: 'onClick description\n@ignore',
});
const { ignore } = flowHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(ignore).toBeTruthy();
});
});
describe('unknown handler', () => {
it('should map defaults docgen info properly', () => {
const docgenInfo = createDocgenInfo();
const { propDef } = unknownHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(propDef.name).toBe(DEFAULT_PROP_NAME);
expect(propDef.type.name).toBe('unknown');
expect(propDef.description).toBe(docgenInfo.description);
expect(propDef.required).toBe(docgenInfo.required);
expect(propDef.defaultValue).toBe(docgenInfo.defaultValue.value);
});
it('should not set ignore to true when the property is not marked with @ignore', () => {
const docgenInfo = createDocgenInfo({
description: 'onClick description',
});
const { ignore } = unknownHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(ignore).toBeFalsy();
});
it('should set ignore to true when the property is marked with @ignore', () => {
const docgenInfo = createDocgenInfo({
description: 'onClick description\n@ignore',
});
const { ignore } = unknownHandler(DEFAULT_PROP_NAME, docgenInfo);
expect(ignore).toBeTruthy();
});
});

View File

@ -1,178 +0,0 @@
import { PropDef } from '@storybook/components';
import { isNil } from 'lodash';
import { parseJsDoc, ExtractedJsDocTags, ExtractedJsDocParamTag } from './jsdoc-parser';
import { DocgenInfo } from './DocgenInfo';
export const TypeSystem = {
Flow: 'Flow',
TypeScript: 'TypeScript',
PropTypes: 'PropTypes',
Unknown: 'Unknown',
};
export interface TypeSystemHandlerResult {
propDef?: PropDef;
ignore: boolean;
}
export type TypeSystemHandler = (
propName: string,
docgenInfo: DocgenInfo
) => TypeSystemHandlerResult;
interface HandlePropResult extends TypeSystemHandlerResult {
extractedJsDocTags?: ExtractedJsDocTags;
}
function createDefaultPropDef(
propName: string,
propType: {
name: string;
},
docgenInfo: DocgenInfo
): PropDef {
const { description, required, defaultValue } = docgenInfo;
return {
name: propName,
type: propType,
required,
description,
defaultValue: isNil(defaultValue) ? null : defaultValue.value,
};
}
function propMightContainsJsDoc(docgenInfo: DocgenInfo): boolean {
return !isNil(docgenInfo.description) && docgenInfo.description.includes('@');
}
function handleProp(
propName: string,
propType: {
name: string;
},
docgenInfo: DocgenInfo
): HandlePropResult {
const propDef = createDefaultPropDef(propName, propType, docgenInfo);
if (propMightContainsJsDoc(docgenInfo)) {
const { ignore, description, extractedTags } = parseJsDoc(docgenInfo);
if (ignore) {
return {
ignore: true,
};
}
if (!isNil(description)) {
propDef.description = description;
}
const hasParams = !isNil(extractedTags.params);
const hasReturns = !isNil(extractedTags.returns) && !isNil(extractedTags.returns.type);
if (hasParams || hasReturns) {
propDef.jsDocTags = {
params: hasParams && extractedTags.params.map(x => x.raw),
returns: hasReturns && extractedTags.returns.raw,
};
return {
propDef,
extractedJsDocTags: extractedTags,
ignore: false,
};
}
}
return {
propDef,
ignore: false,
};
}
export const propTypesHandler: TypeSystemHandler = (propName: string, docgenInfo: DocgenInfo) => {
const result = handleProp(propName, docgenInfo.type, docgenInfo);
if (!result.ignore) {
const { propDef, extractedJsDocTags } = result;
// When available use the proper JSDoc tags to describe the function signature instead of displaying "func".
if (propDef.type.name === 'func') {
if (!isNil(extractedJsDocTags)) {
const hasParams = !isNil(extractedJsDocTags.params);
const hasReturns = !isNil(extractedJsDocTags.returns);
if (hasParams || hasReturns) {
const funcParts = [];
if (hasParams) {
const funcParams = extractedJsDocTags.params.map((x: ExtractedJsDocParamTag) => {
const prettyName = x.getPrettyName();
const typeName = x.getTypeName();
if (!isNil(typeName)) {
return `${prettyName}: ${typeName}`;
}
return prettyName;
});
funcParts.push(`(${funcParams.join(', ')})`);
} else {
funcParts.push('()');
}
if (hasReturns) {
funcParts.push(`=> ${extractedJsDocTags.returns.getTypeName()}`);
}
propDef.type = {
name: funcParts.join(' '),
};
}
}
}
}
return result;
};
export const tsHandler: TypeSystemHandler = (propName: string, docgenInfo: DocgenInfo) => {
return handleProp(propName, docgenInfo.tsType, docgenInfo);
};
export const flowHandler: TypeSystemHandler = (propName: string, docgenInfo: DocgenInfo) => {
return handleProp(propName, docgenInfo.flowType, docgenInfo);
};
export const unknownHandler: TypeSystemHandler = (propName: string, docgenInfo: DocgenInfo) => {
return handleProp(propName, { name: 'unknown' }, docgenInfo);
};
export const TypeSystemHandlers: Record<string, TypeSystemHandler> = {
[TypeSystem.Flow]: flowHandler,
[TypeSystem.TypeScript]: tsHandler,
[TypeSystem.PropTypes]: propTypesHandler,
[TypeSystem.Unknown]: unknownHandler,
};
export const getPropTypeSystem = (docgenInfo: DocgenInfo): string => {
if (!isNil(docgenInfo.flowType)) {
return TypeSystem.Flow;
}
if (!isNil(docgenInfo.tsType)) {
return TypeSystem.TypeScript;
}
if (!isNil(docgenInfo.type)) {
return TypeSystem.PropTypes;
}
return TypeSystem.Unknown;
};
export const getTypeSystemHandler = (typeSystem: string): TypeSystemHandler => {
return TypeSystemHandlers[typeSystem];
};

View File

@ -1,849 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin decorators.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta
title=\\"Button\\"
decorators={[
storyFn => (
<div
style={{
backgroundColor: 'yellow',
}}
>
{storyFn()}
</div>
),
]}
mdxType=\\"Meta\\"
/>
<h1>{\`Decorated story\`}</h1>
<Story
name=\\"one\\"
decorators={[storyFn => <div className=\\"local\\">{storyFn()}</div>]}
mdxType=\\"Story\\"
>
<Button mdxType=\\"Button\\">One</Button>
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const one = () => <Button>One</Button>;
one.story = {};
one.story.name = 'one';
one.story.parameters = { mdxSource: '<Button>One</Button>' };
one.story.decorators = [storyFn => <div className=\\"local\\">{storyFn()}</div>];
const componentMeta = {
title: 'Button',
decorators: [
storyFn => (
<div
style={{
backgroundColor: 'yellow',
}}
>
{storyFn()}
</div>
),
],
includeStories: ['one'],
};
const mdxStoryNameToId = { one: 'button--one' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin docs-only.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Meta } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"docs-only\\" mdxType=\\"Meta\\" />
<h1>{\`Documentation only\`}</h1>
<p>
{\`This is a documentation-only MDX file which generates a dummy \`}
<inlineCode parentName=\\"p\\">{\`docsOnly: true\`}</inlineCode>
{\` story.\`}
</p>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const __page = () => {
throw new Error('Docs-only story');
};
__page.story = { parameters: { docsOnly: true } };
const componentMeta = { title: 'docs-only', includeStories: ['__page'] };
const mdxStoryNameToId = {};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin non-story-exports.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs/blocks';
export const two = 2;
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {
two,
};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"Button\\" mdxType=\\"Meta\\" />
<h1>{\`Story definition\`}</h1>
<Story name=\\"one\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">One</Button>
</Story>
<Story name=\\"hello story\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">Hello button</Button>
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const one = () => <Button>One</Button>;
one.story = {};
one.story.name = 'one';
one.story.parameters = { mdxSource: '<Button>One</Button>' };
export const helloStory = () => <Button>Hello button</Button>;
helloStory.story = {};
helloStory.story.name = 'hello story';
helloStory.story.parameters = { mdxSource: '<Button>Hello button</Button>' };
const componentMeta = { title: 'Button', includeStories: ['one', 'helloStory'] };
const mdxStoryNameToId = { one: 'button--one', 'hello story': 'button--hello-story' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin parameters.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta
title=\\"Button\\"
component={Button}
parameters={{
notes: 'component notes',
}}
mdxType=\\"Meta\\"
/>
<Story name=\\"component notes\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">Component notes</Button>
</Story>
<Story
name=\\"story notes\\"
parameters={{
notes: 'story notes',
}}
mdxType=\\"Story\\"
>
<Button mdxType=\\"Button\\">Story notes</Button>
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const componentNotes = () => <Button>Component notes</Button>;
componentNotes.story = {};
componentNotes.story.name = 'component notes';
componentNotes.story.parameters = { mdxSource: '<Button>Component notes</Button>' };
export const storyNotes = () => <Button>Story notes</Button>;
storyNotes.story = {};
storyNotes.story.name = 'story notes';
storyNotes.story.parameters = {
mdxSource: '<Button>Story notes</Button>',
...{
notes: 'story notes',
},
};
const componentMeta = {
title: 'Button',
parameters: {
notes: 'component notes',
},
includeStories: ['componentNotes', 'storyNotes'],
};
const mdxStoryNameToId = {
'component notes': 'button--component-notes',
'story notes': 'button--story-notes',
};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin previews.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Button } from '@storybook/react/demo';
import { Preview, Story, Meta } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta
title=\\"Button\\"
component={Button}
parameters={{
notes: 'component notes',
}}
mdxType=\\"Meta\\"
/>
<h1>{\`Preview\`}</h1>
<p>{\`Previews can contain normal components, stories, and story references\`}</p>
<Preview mdxType=\\"Preview\\">
<Button mdxType=\\"Button\\">Just a button</Button>
<Story name=\\"hello button\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">Hello button</Button>
</Story>
<Story name=\\"two\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">Two</Button>
</Story>
<Story id=\\"welcome--welcome\\" mdxType=\\"Story\\" />
</Preview>
<p>{\`Preview wthout a story\`}</p>
<Preview mdxSource=\\"%0A%3CButton%3EJust%20a%20button%3C/Button%3E%0A\\" mdxType=\\"Preview\\">
<Button mdxType=\\"Button\\">Just a button</Button>
</Preview>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const helloButton = () => <Button>Hello button</Button>;
helloButton.story = {};
helloButton.story.name = 'hello button';
helloButton.story.parameters = { mdxSource: '<Button>Hello button</Button>' };
export const two = () => <Button>Two</Button>;
two.story = {};
two.story.name = 'two';
two.story.parameters = { mdxSource: '<Button>Two</Button>' };
const componentMeta = {
title: 'Button',
parameters: {
notes: 'component notes',
},
includeStories: ['helloButton', 'two'],
};
const mdxStoryNameToId = { 'hello button': 'button--hello-button', two: 'button--two' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin story-current.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Story } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<h1>{\`Current story\`}</h1>
<Story id=\\".\\" mdxType=\\"Story\\" />
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
const componentMeta = { includeStories: [] };
const mdxStoryNameToId = {};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin story-def-text-only.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Story, Meta } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"Text\\" mdxType=\\"Meta\\" />
<h1>{\`Story definition\`}</h1>
<Story name=\\"text\\" mdxType=\\"Story\\">
Plain text
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const text = makeStoryFn('Plain text');
text.story = {};
text.story.name = 'text';
text.story.parameters = { mdxSource: \\"'Plain text'\\" };
const componentMeta = { title: 'Text', includeStories: ['text'] };
const mdxStoryNameToId = { text: 'text--text' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin story-definitions.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"Button\\" mdxType=\\"Meta\\" />
<h1>{\`Story definition\`}</h1>
<Story name=\\"one\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">One</Button>
</Story>
<Story name=\\"hello story\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">Hello button</Button>
</Story>
<Story name=\\"w/punctuation\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">with punctuation</Button>
</Story>
<Story name=\\"1 fine day\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">starts with number</Button>
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const one = () => <Button>One</Button>;
one.story = {};
one.story.name = 'one';
one.story.parameters = { mdxSource: '<Button>One</Button>' };
export const helloStory = () => <Button>Hello button</Button>;
helloStory.story = {};
helloStory.story.name = 'hello story';
helloStory.story.parameters = { mdxSource: '<Button>Hello button</Button>' };
export const wPunctuation = () => <Button>with punctuation</Button>;
wPunctuation.story = {};
wPunctuation.story.name = 'w/punctuation';
wPunctuation.story.parameters = { mdxSource: '<Button>with punctuation</Button>' };
export const _1FineDay = () => <Button>starts with number</Button>;
_1FineDay.story = {};
_1FineDay.story.name = '1 fine day';
_1FineDay.story.parameters = { mdxSource: '<Button>starts with number</Button>' };
const componentMeta = {
title: 'Button',
includeStories: ['one', 'helloStory', 'wPunctuation', '_1FineDay'],
};
const mdxStoryNameToId = {
one: 'button--one',
'hello story': 'button--hello-story',
'w/punctuation': 'button--w-punctuation',
'1 fine day': 'button--1-fine-day',
};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin story-function.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const Story = makeShortcode('Story');
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Story name=\\"function\\" height=\\"100px\\" mdxType=\\"Story\\">
{() => {
const btn = document.createElement('button');
btn.innerHTML = 'Hello Button';
btn.addEventListener('click', action('Click'));
return btn;
}}
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const functionStory = makeStoryFn(() => {
const btn = document.createElement('button');
btn.innerHTML = 'Hello Button';
btn.addEventListener('click', action('Click'));
return btn;
});
functionStory.story = {};
functionStory.story.name = 'function';
functionStory.story.parameters = {
mdxSource:
\\"() => {\\\\n const btn = document.createElement('button');\\\\n btn.innerHTML = 'Hello Button';\\\\n btn.addEventListener('click', action('Click'));\\\\n return btn;\\\\n}\\",
};
const componentMeta = { includeStories: ['functionStory'] };
const mdxStoryNameToId = {};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin story-function-var.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Meta, Story } from '@storybook/addon-docs/blocks';
export const basicFn = () => <Button mdxType=\\"Button\\" />;
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const Button = makeShortcode('Button');
const layoutProps = {
basicFn,
};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"story-function-var\\" mdxType=\\"Meta\\" />
<h1>{\`Button\`}</h1>
<p>{\`I can define a story with the function defined in CSF:\`}</p>
<Story name=\\"basic\\" mdxType=\\"Story\\">
{basicFn}
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const basic = makeStoryFn(basicFn);
basic.story = {};
basic.story.name = 'basic';
basic.story.parameters = { mdxSource: 'basicFn' };
const componentMeta = { title: 'story-function-var', includeStories: ['basic'] };
const mdxStoryNameToId = { basic: 'story-function-var--basic' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin story-object.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Story, Meta } from '@storybook/addon-docs/blocks';
import { Welcome, Button } from '@storybook/angular/demo';
import { linkTo } from '@storybook/addon-links';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"MDX|Welcome\\" mdxType=\\"Meta\\" />
<h1>{\`Story object\`}</h1>
<Story name=\\"to storybook\\" height=\\"300px\\" mdxType=\\"Story\\">
{{
template: \`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\`,
props: {
showApp: linkTo('Button'),
},
moduleMetadata: {
declarations: [Welcome],
},
}}
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const toStorybook = makeStoryFn({
template: \`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\`,
props: {
showApp: linkTo('Button'),
},
moduleMetadata: {
declarations: [Welcome],
},
});
toStorybook.story = {};
toStorybook.story.name = 'to storybook';
toStorybook.story.parameters = {
mdxSource:
'{\\\\n template: \`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\`,\\\\n props: {\\\\n showApp: linkTo(\\\\'Button\\\\')\\\\n },\\\\n moduleMetadata: {\\\\n declarations: [Welcome]\\\\n }\\\\n}',
};
const componentMeta = { title: 'MDX|Welcome', includeStories: ['toStorybook'] };
const mdxStoryNameToId = { 'to storybook': 'mdx-welcome--to-storybook' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin story-references.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Story } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<h1>{\`Story reference\`}</h1>
<Story id=\\"welcome--welcome\\" mdxType=\\"Story\\" />
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
const componentMeta = { includeStories: [] };
const mdxStoryNameToId = {};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin vanilla.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Button } from '@storybook/react/demo';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<h1>{\`Hello MDX\`}</h1>
<p>{\`This is some random content.\`}</p>
<Button mdxType=\\"Button\\">Hello button</Button>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
const componentMeta = { includeStories: [] };
const mdxStoryNameToId = {};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,8 @@
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs/blocks';
<Meta title="Button" component={Button} id="button-id" />
<Story name="component notes">
<Button>Component notes</Button>
</Story>

View File

@ -0,0 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin component-id.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"Button\\" component={Button} id=\\"button-id\\" mdxType=\\"Meta\\" />
<Story name=\\"component notes\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">Component notes</Button>
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const componentNotes = () => <Button>Component notes</Button>;
componentNotes.story = {};
componentNotes.story.name = 'component notes';
componentNotes.story.parameters = { mdxSource: '<Button>Component notes</Button>' };
const componentMeta = { title: 'Button', id: 'button-id', includeStories: ['componentNotes'] };
const mdxStoryNameToId = { 'component notes': 'button--component-notes' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,88 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin decorators.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta
title=\\"Button\\"
decorators={[
storyFn => (
<div
style={{
backgroundColor: 'yellow',
}}
>
{storyFn()}
</div>
),
]}
mdxType=\\"Meta\\"
/>
<h1>{\`Decorated story\`}</h1>
<Story
name=\\"one\\"
decorators={[storyFn => <div className=\\"local\\">{storyFn()}</div>]}
mdxType=\\"Story\\"
>
<Button mdxType=\\"Button\\">One</Button>
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const one = () => <Button>One</Button>;
one.story = {};
one.story.name = 'one';
one.story.parameters = { mdxSource: '<Button>One</Button>' };
one.story.decorators = [storyFn => <div className=\\"local\\">{storyFn()}</div>];
const componentMeta = {
title: 'Button',
decorators: [
storyFn => (
<div
style={{
backgroundColor: 'yellow',
}}
>
{storyFn()}
</div>
),
],
includeStories: ['one'],
};
const mdxStoryNameToId = { one: 'button--one' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin docs-only.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Meta } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"docs-only\\" mdxType=\\"Meta\\" />
<h1>{\`Documentation only\`}</h1>
<p>
{\`This is a documentation-only MDX file which generates a dummy \`}
<inlineCode parentName=\\"p\\">{\`docsOnly: true\`}</inlineCode>
{\` story.\`}
</p>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const __page = () => {
throw new Error('Docs-only story');
};
__page.story = { parameters: { docsOnly: true } };
const componentMeta = { title: 'docs-only', includeStories: ['__page'] };
const mdxStoryNameToId = {};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,66 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin non-story-exports.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs/blocks';
export const two = 2;
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {
two,
};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"Button\\" mdxType=\\"Meta\\" />
<h1>{\`Story definition\`}</h1>
<Story name=\\"one\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">One</Button>
</Story>
<Story name=\\"hello story\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">Hello button</Button>
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const one = () => <Button>One</Button>;
one.story = {};
one.story.name = 'one';
one.story.parameters = { mdxSource: '<Button>One</Button>' };
export const helloStory = () => <Button>Hello button</Button>;
helloStory.story = {};
helloStory.story.name = 'hello story';
helloStory.story.parameters = { mdxSource: '<Button>Hello button</Button>' };
const componentMeta = { title: 'Button', includeStories: ['one', 'helloStory'] };
const mdxStoryNameToId = { one: 'button--one', 'hello story': 'button--hello-story' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,89 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin parameters.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta
title=\\"Button\\"
component={Button}
parameters={{
notes: 'component notes',
}}
mdxType=\\"Meta\\"
/>
<Story name=\\"component notes\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">Component notes</Button>
</Story>
<Story
name=\\"story notes\\"
parameters={{
notes: 'story notes',
}}
mdxType=\\"Story\\"
>
<Button mdxType=\\"Button\\">Story notes</Button>
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const componentNotes = () => <Button>Component notes</Button>;
componentNotes.story = {};
componentNotes.story.name = 'component notes';
componentNotes.story.parameters = { mdxSource: '<Button>Component notes</Button>' };
export const storyNotes = () => <Button>Story notes</Button>;
storyNotes.story = {};
storyNotes.story.name = 'story notes';
storyNotes.story.parameters = {
mdxSource: '<Button>Story notes</Button>',
...{
notes: 'story notes',
},
};
const componentMeta = {
title: 'Button',
parameters: {
notes: 'component notes',
},
includeStories: ['componentNotes', 'storyNotes'],
};
const mdxStoryNameToId = {
'component notes': 'button--component-notes',
'story notes': 'button--story-notes',
};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,85 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin previews.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Button } from '@storybook/react/demo';
import { Preview, Story, Meta } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta
title=\\"Button\\"
component={Button}
parameters={{
notes: 'component notes',
}}
mdxType=\\"Meta\\"
/>
<h1>{\`Preview\`}</h1>
<p>{\`Previews can contain normal components, stories, and story references\`}</p>
<Preview mdxType=\\"Preview\\">
<Button mdxType=\\"Button\\">Just a button</Button>
<Story name=\\"hello button\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">Hello button</Button>
</Story>
<Story name=\\"two\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">Two</Button>
</Story>
<Story id=\\"welcome--welcome\\" mdxType=\\"Story\\" />
</Preview>
<p>{\`Preview wthout a story\`}</p>
<Preview mdxSource=\\"%0A%3CButton%3EJust%20a%20button%3C/Button%3E%0A\\" mdxType=\\"Preview\\">
<Button mdxType=\\"Button\\">Just a button</Button>
</Preview>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const helloButton = () => <Button>Hello button</Button>;
helloButton.story = {};
helloButton.story.name = 'hello button';
helloButton.story.parameters = { mdxSource: '<Button>Hello button</Button>' };
export const two = () => <Button>Two</Button>;
two.story = {};
two.story.name = 'two';
two.story.parameters = { mdxSource: '<Button>Two</Button>' };
const componentMeta = {
title: 'Button',
parameters: {
notes: 'component notes',
},
includeStories: ['helloButton', 'two'],
};
const mdxStoryNameToId = { 'hello button': 'button--hello-button', two: 'button--two' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,46 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin story-current.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Story } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<h1>{\`Current story\`}</h1>
<Story id=\\".\\" mdxType=\\"Story\\" />
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
const componentMeta = { includeStories: [] };
const mdxStoryNameToId = {};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin story-def-text-only.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Story, Meta } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"Text\\" mdxType=\\"Meta\\" />
<h1>{\`Story definition\`}</h1>
<Story name=\\"text\\" mdxType=\\"Story\\">
Plain text
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const text = makeStoryFn('Plain text');
text.story = {};
text.story.name = 'text';
text.story.parameters = { mdxSource: \\"'Plain text'\\" };
const componentMeta = { title: 'Text', includeStories: ['text'] };
const mdxStoryNameToId = { text: 'text--text' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,87 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin story-definitions.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"Button\\" mdxType=\\"Meta\\" />
<h1>{\`Story definition\`}</h1>
<Story name=\\"one\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">One</Button>
</Story>
<Story name=\\"hello story\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">Hello button</Button>
</Story>
<Story name=\\"w/punctuation\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">with punctuation</Button>
</Story>
<Story name=\\"1 fine day\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">starts with number</Button>
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const one = () => <Button>One</Button>;
one.story = {};
one.story.name = 'one';
one.story.parameters = { mdxSource: '<Button>One</Button>' };
export const helloStory = () => <Button>Hello button</Button>;
helloStory.story = {};
helloStory.story.name = 'hello story';
helloStory.story.parameters = { mdxSource: '<Button>Hello button</Button>' };
export const wPunctuation = () => <Button>with punctuation</Button>;
wPunctuation.story = {};
wPunctuation.story.name = 'w/punctuation';
wPunctuation.story.parameters = { mdxSource: '<Button>with punctuation</Button>' };
export const _1FineDay = () => <Button>starts with number</Button>;
_1FineDay.story = {};
_1FineDay.story.name = '1 fine day';
_1FineDay.story.parameters = { mdxSource: '<Button>starts with number</Button>' };
const componentMeta = {
title: 'Button',
includeStories: ['one', 'helloStory', 'wPunctuation', '_1FineDay'],
};
const mdxStoryNameToId = {
one: 'button--one',
'hello story': 'button--hello-story',
'w/punctuation': 'button--w-punctuation',
'1 fine day': 'button--1-fine-day',
};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,58 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin story-function-var.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Meta, Story } from '@storybook/addon-docs/blocks';
export const basicFn = () => <Button mdxType=\\"Button\\" />;
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const Button = makeShortcode('Button');
const layoutProps = {
basicFn,
};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"story-function-var\\" mdxType=\\"Meta\\" />
<h1>{\`Button\`}</h1>
<p>{\`I can define a story with the function defined in CSF:\`}</p>
<Story name=\\"basic\\" mdxType=\\"Story\\">
{basicFn}
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const basic = makeStoryFn(basicFn);
basic.story = {};
basic.story.name = 'basic';
basic.story.parameters = { mdxSource: 'basicFn' };
const componentMeta = { title: 'story-function-var', includeStories: ['basic'] };
const mdxStoryNameToId = { basic: 'story-function-var--basic' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,63 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin story-function.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const Story = makeShortcode('Story');
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Story name=\\"function\\" height=\\"100px\\" mdxType=\\"Story\\">
{() => {
const btn = document.createElement('button');
btn.innerHTML = 'Hello Button';
btn.addEventListener('click', action('Click'));
return btn;
}}
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const functionStory = makeStoryFn(() => {
const btn = document.createElement('button');
btn.innerHTML = 'Hello Button';
btn.addEventListener('click', action('Click'));
return btn;
});
functionStory.story = {};
functionStory.story.name = 'function';
functionStory.story.parameters = {
mdxSource:
\\"() => {\\\\n const btn = document.createElement('button');\\\\n btn.innerHTML = 'Hello Button';\\\\n btn.addEventListener('click', action('Click'));\\\\n return btn;\\\\n}\\",
};
const componentMeta = { includeStories: ['functionStory'] };
const mdxStoryNameToId = {};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,75 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin story-object.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Story, Meta } from '@storybook/addon-docs/blocks';
import { Welcome, Button } from '@storybook/angular/demo';
import { linkTo } from '@storybook/addon-links';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"MDX|Welcome\\" mdxType=\\"Meta\\" />
<h1>{\`Story object\`}</h1>
<Story name=\\"to storybook\\" height=\\"300px\\" mdxType=\\"Story\\">
{{
template: \`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\`,
props: {
showApp: linkTo('Button'),
},
moduleMetadata: {
declarations: [Welcome],
},
}}
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const toStorybook = makeStoryFn({
template: \`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\`,
props: {
showApp: linkTo('Button'),
},
moduleMetadata: {
declarations: [Welcome],
},
});
toStorybook.story = {};
toStorybook.story.name = 'to storybook';
toStorybook.story.parameters = {
mdxSource:
'{\\\\n template: \`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\`,\\\\n props: {\\\\n showApp: linkTo(\\\\'Button\\\\')\\\\n },\\\\n moduleMetadata: {\\\\n declarations: [Welcome]\\\\n }\\\\n}',
};
const componentMeta = { title: 'MDX|Welcome', includeStories: ['toStorybook'] };
const mdxStoryNameToId = { 'to storybook': 'mdx-welcome--to-storybook' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,46 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin story-references.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Story } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<h1>{\`Story reference\`}</h1>
<Story id=\\"welcome--welcome\\" mdxType=\\"Story\\" />
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
const componentMeta = { includeStories: [] };
const mdxStoryNameToId = {};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,47 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin vanilla.mdx 1`] = `
"/* @jsx mdx */
import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks';
import { Button } from '@storybook/react/demo';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<h1>{\`Hello MDX\`}</h1>
<p>{\`This is some random content.\`}</p>
<Button mdxType=\\"Button\\">Hello button</Button>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
const componentMeta = { includeStories: [] };
const mdxStoryNameToId = {};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
container: ({ context, children }) => (
<DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer>
),
page: MDXContent,
};
export default componentMeta;
"
`;

View File

@ -130,9 +130,11 @@ function genPreviewExports(ast, context) {
function genMeta(ast) {
let title = getAttr(ast.openingElement, 'title');
let id = getAttr(ast.openingElement, 'id');
let parameters = getAttr(ast.openingElement, 'parameters');
let decorators = getAttr(ast.openingElement, 'decorators');
title = title && `'${title.value}'`;
id = id && `'${id.value}'`;
if (parameters && parameters.expression) {
const { code: params } = generate(parameters.expression, {});
parameters = params;
@ -143,6 +145,7 @@ function genMeta(ast) {
}
return {
title,
id,
parameters,
decorators,
};

View File

@ -1,8 +1,9 @@
const path = require('path');
const fs = require('fs-extra');
const mdx = require('@mdx-js/mdx');
const prettier = require('prettier');
const plugin = require('./mdx-compiler-plugin');
import 'jest-specific-snapshot';
import path from 'path';
import fs from 'fs-extra';
import mdx from '@mdx-js/mdx';
import prettier from 'prettier';
import plugin from './mdx-compiler-plugin';
async function generate(filePath) {
const content = await fs.readFile(filePath, 'utf8');
@ -22,28 +23,20 @@ async function generate(filePath) {
});
}
const inputRegExp = /\.mdx$/;
describe('docs-mdx-compiler-plugin', () => {
const fixtures = [
'vanilla.mdx',
'story-definitions.mdx',
'story-def-text-only.mdx',
'story-object.mdx',
'story-references.mdx',
'story-current.mdx',
'previews.mdx',
'decorators.mdx',
'parameters.mdx',
'non-story-exports.mdx',
'story-function.mdx',
'docs-only.mdx',
'story-function-var.mdx',
];
fixtures.forEach(fixtureFile => {
it(fixtureFile, async () => {
const code = await generate(path.resolve(__dirname, `./__testfixtures__/${fixtureFile}`));
expect(code).toMatchSnapshot();
const transformFixturesDir = path.join(__dirname, '__testfixtures__');
fs.readdirSync(transformFixturesDir)
.filter(fileName => inputRegExp.test(fileName))
.filter(fileName => fileName !== 'story-missing-props.mdx')
.forEach(fixtureFile => {
it(fixtureFile, async () => {
const inputPath = path.join(transformFixturesDir, fixtureFile);
const code = await generate(inputPath);
expect(code).toMatchSpecificSnapshot(inputPath.replace(inputRegExp, '.output.snapshot'));
});
});
});
it('errors on missing story props', async () => {
await expect(
generate(path.resolve(__dirname, './__testfixtures__/story-missing-props.mdx'))

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-events",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Add events to your Storybook stories.",
"keywords": [
"addon",
@ -31,11 +31,11 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/client-api": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/theming": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"@storybook/client-api": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"@storybook/theming": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"format-json": "^1.0.3",
"lodash": "^4.17.15",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-google-analytics",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Storybook addon for google analytics",
"keywords": [
"addon",
@ -20,8 +20,8 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"global": "^4.3.2",
"react-ga": "^2.5.7"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-graphql",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Storybook addon to display the GraphiQL IDE",
"keywords": [
"addon",
@ -29,8 +29,8 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"global": "^4.3.2",
"graphiql": "^0.16.0",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-info",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "A Storybook addon to show additional information for your stories.",
"keywords": [
"addon",
@ -28,10 +28,10 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/client-logger": "5.3.0-alpha.44",
"@storybook/components": "5.3.0-alpha.44",
"@storybook/theming": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/client-logger": "5.3.0-alpha.45",
"@storybook/components": "5.3.0-alpha.45",
"@storybook/theming": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"global": "^4.3.2",
"marksy": "^7.0.0",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-jest",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "React storybook addon that show component jest report",
"keywords": [
"addon",
@ -35,11 +35,11 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/components": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/theming": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"@storybook/components": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"@storybook/theming": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"global": "^4.3.2",
"react": "^16.8.3",

View File

@ -307,8 +307,37 @@ const groupId = 'GROUP-ID1';
const value = select(label, options, defaultValue, groupId);
```
> You can also provide options as an array like this: `['red', 'blue', 'yellow']`.
Options can also be an array:
```js
import { select } from '@storybook/addon-knobs';
const label = 'Cats';
const options = ['linus', 'eleanor', 'lover']
const defaultValue = 'eleanor';
const groupId = 'GROUP-ID2';
const value = select(label, options, defaultValue, groupId);
```
Options can also be an array OF objects:
```js
const label = 'Dogs';
const arrayOfObjects = [
{
label: 'Sparky',
dogParent: 'Matthew',
location: 'Austin',
},
{
label: 'Juniper',
dogParent: 'Joshua',
location: 'Austin',
},
];
const defaultValue = arrayOfObjects[0];
const groupId = 'GROUP-ID3';
const value = select(label, options, defaultValue, groupId);
```
### radio buttons

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-knobs",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Storybook Addon Prop Editor Component",
"keywords": [
"addon",
@ -29,12 +29,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/client-api": "5.3.0-alpha.44",
"@storybook/components": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/theming": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"@storybook/client-api": "5.3.0-alpha.45",
"@storybook/components": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"@storybook/theming": "5.3.0-alpha.45",
"@types/react-color": "^3.0.1",
"copy-to-clipboard": "^3.0.8",
"core-js": "^3.0.1",

View File

@ -30,15 +30,23 @@ const SelectType: FunctionComponent<SelectTypeProps> & {
deserialize: typeof deserialize;
} = ({ knob, onChange }) => {
const { options } = knob;
const entries = Array.isArray(options)
? options.reduce<Record<PropertyKey, SelectTypeKnobValue>>((acc, k) => ({ ...acc, [k]: k }), {})
: (options as Record<PropertyKey, SelectTypeKnobValue>);
const selectedKey = Object.keys(entries).find(k => {
if (Array.isArray(knob.value)) {
return JSON.stringify(entries[k]) === JSON.stringify(knob.value);
const callbackReduceArrayOptions = (acc: any, option: any, i: number) => {
if (typeof option !== 'object') return { ...acc, [option]: option };
const label = option.label || option.key || i;
return { ...acc, [label]: option };
};
const entries = Array.isArray(options) ? options.reduce(callbackReduceArrayOptions, {}) : options;
const selectedKey = Object.keys(entries).find(key => {
const { value: knobVal } = knob;
const entryVal = entries[key];
if (Array.isArray(knobVal)) {
return JSON.stringify(entryVal) === JSON.stringify(knobVal);
}
return entries[k] === knob.value;
return entryVal === knobVal;
});
return (

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-links",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Story Links addon for storybook",
"keywords": [
"addon",
@ -29,10 +29,10 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/client-logger": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/router": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/client-logger": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"@storybook/router": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"global": "^4.3.2",
"prop-types": "^15.7.2",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-notes",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Write notes for your Storybook stories.",
"keywords": [
"addon",
@ -30,13 +30,13 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/client-logger": "5.3.0-alpha.44",
"@storybook/components": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/router": "5.3.0-alpha.44",
"@storybook/theming": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"@storybook/client-logger": "5.3.0-alpha.45",
"@storybook/components": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"@storybook/router": "5.3.0-alpha.45",
"@storybook/theming": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"global": "^4.3.2",
"markdown-to-jsx": "^6.10.3",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-ondevice-actions",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Action Logger addon for react-native storybook",
"keywords": [
"storybook"
@ -26,13 +26,13 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"fast-deep-equal": "^2.0.1"
},
"devDependencies": {
"@storybook/addon-actions": "5.3.0-alpha.44"
"@storybook/addon-actions": "5.3.0-alpha.45"
},
"peerDependencies": {
"@storybook/addon-actions": "*",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-ondevice-backgrounds",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "A react-native storybook addon to show different backgrounds for your preview",
"keywords": [
"addon",
@ -31,9 +31,9 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/client-api": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"@storybook/client-api": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"prop-types": "^15.7.2"
},

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-ondevice-knobs",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Display storybook story knobs on your deviced.",
"keywords": [
"addon",
@ -28,8 +28,8 @@
},
"dependencies": {
"@emotion/native": "^10.0.14",
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"deep-equal": "^1.0.1",
"prop-types": "^15.7.2",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-ondevice-notes",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Write notes for your react-native Storybook stories.",
"keywords": [
"addon",
@ -28,11 +28,11 @@
},
"dependencies": {
"@emotion/core": "^10.0.20",
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/client-api": "5.3.0-alpha.44",
"@storybook/client-logger": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"@storybook/client-api": "5.3.0-alpha.45",
"@storybook/client-logger": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"prop-types": "^15.7.2",
"react-native-simple-markdown": "^1.1.0"

View File

@ -75,23 +75,10 @@ addParameters({
*/
addonPanelInRight: false,
/**
* regex for finding the hierarchy separator
* @example:
* null - turn off hierarchy
* /\// - split by `/`
* /\./ - split by `.`
* /\/|\./ - split by `/` or `.`
* @type {Regex}
* display the top-level grouping as a "root" in the sidebar
* @type {Boolean}
*/
hierarchySeparator: null,
/**
* regex for finding the hierarchy root separator
* @example:
* null - turn off multiple hierarchy roots
* /\|/ - split by `|`
* @type {Regex}
*/
hierarchyRootSeparator: null,
showRoots: null,
/**
* sidebar tree animations
* @type {Boolean}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-options",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "Options addon for storybook",
"keywords": [
"addon",
@ -29,7 +29,7 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"util-deprecate": "^1.0.2"
},

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-queryparams",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "parameter addon for storybook",
"keywords": [
"addon",
@ -30,12 +30,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/api": "5.3.0-alpha.44",
"@storybook/client-logger": "5.3.0-alpha.44",
"@storybook/components": "5.3.0-alpha.44",
"@storybook/core-events": "5.3.0-alpha.44",
"@storybook/theming": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/api": "5.3.0-alpha.45",
"@storybook/client-logger": "5.3.0-alpha.45",
"@storybook/components": "5.3.0-alpha.45",
"@storybook/core-events": "5.3.0-alpha.45",
"@storybook/theming": "5.3.0-alpha.45",
"core-js": "^3.0.1",
"global": "^4.3.2",
"qs": "^6.6.0",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storyshots",
"version": "5.3.0-alpha.44",
"version": "5.3.0-alpha.45",
"description": "StoryShots is a Jest Snapshot Testing Addon for Storybook.",
"keywords": [
"addon",
@ -33,8 +33,8 @@
},
"dependencies": {
"@jest/transform": "^24.9.0",
"@storybook/addons": "5.3.0-alpha.44",
"@storybook/client-api": "5.3.0-alpha.44",
"@storybook/addons": "5.3.0-alpha.45",
"@storybook/client-api": "5.3.0-alpha.45",
"@types/glob": "^7.1.1",
"@types/jest": "^24.0.16",
"@types/jest-specific-snapshot": "^0.5.3",
@ -47,8 +47,8 @@
"ts-dedent": "^1.1.0"
},
"devDependencies": {
"@storybook/addon-docs": "5.3.0-alpha.44",
"@storybook/react": "5.3.0-alpha.44",
"@storybook/addon-docs": "5.3.0-alpha.45",
"@storybook/react": "5.3.0-alpha.45",
"babel-loader": "^8.0.6",
"enzyme-to-json": "^3.4.1",
"jest-emotion": "^10.0.17",

View File

@ -53,13 +53,37 @@ exports[`Storyshots Another Button with text 1`] = `
</Button>
`;
exports[`Storyshots Async With Timeout 1`] = `
exports[`Storyshots Async with 5ms timeout simulating async operation 1`] = `
<AsyncTestComponent>
<h1 />
</AsyncTestComponent>
`;
exports[`Storyshots Button With Some Emoji 1`] = `
exports[`Storyshots Button With Text 1`] = `
<Button
onClick={[Function]}
>
<button
onClick={[Function]}
style={
Object {
"backgroundColor": "#FFFFFF",
"border": "1px solid #eee",
"borderRadius": 3,
"cursor": "pointer",
"fontSize": 15,
"margin": 10,
"padding": "3px 10px",
}
}
type="button"
>
Hello Button
</button>
</Button>
`;
exports[`Storyshots Button with some emoji 1`] = `
<Button
onClick={[Function]}
>
@ -88,31 +112,7 @@ exports[`Storyshots Button With Some Emoji 1`] = `
</Button>
`;
exports[`Storyshots Button With Text 1`] = `
<Button
onClick={[Function]}
>
<button
onClick={[Function]}
style={
Object {
"backgroundColor": "#FFFFFF",
"border": "1px solid #eee",
"borderRadius": 3,
"cursor": "pointer",
"fontSize": 15,
"margin": 10,
"padding": "3px 10px",
}
}
type="button"
>
Hello Button
</button>
</Button>
`;
exports[`Storyshots Welcome MDX To Storybook 1`] = `
exports[`Storyshots Welcome MDX to Storybook 1`] = `
<Welcome
showApp={[Function]}
>
@ -316,7 +316,7 @@ exports[`Storyshots Welcome MDX To Storybook 1`] = `
</Welcome>
`;
exports[`Storyshots Welcome To Storybook 1`] = `
exports[`Storyshots Welcome to Storybook 1`] = `
<Welcome
showApp={[Function]}
>

View File

@ -45,13 +45,33 @@ exports[`Storyshots Another Button with text 1`] = `
</button>
`;
exports[`Storyshots Async With Timeout 1`] = `
exports[`Storyshots Async with 5ms timeout simulating async operation 1`] = `
<h1>
</h1>
`;
exports[`Storyshots Button With Some Emoji 1`] = `
exports[`Storyshots Button With Text 1`] = `
<button
onClick={[Function]}
style={
Object {
"backgroundColor": "#FFFFFF",
"border": "1px solid #eee",
"borderRadius": 3,
"cursor": "pointer",
"fontSize": 15,
"margin": 10,
"padding": "3px 10px",
}
}
type="button"
>
Hello Button
</button>
`;
exports[`Storyshots Button with some emoji 1`] = `
<button
onClick={[Function]}
style={
@ -76,27 +96,7 @@ exports[`Storyshots Button With Some Emoji 1`] = `
</button>
`;
exports[`Storyshots Button With Text 1`] = `
<button
onClick={[Function]}
style={
Object {
"backgroundColor": "#FFFFFF",
"border": "1px solid #eee",
"borderRadius": 3,
"cursor": "pointer",
"fontSize": 15,
"margin": 10,
"padding": "3px 10px",
}
}
type="button"
>
Hello Button
</button>
`;
exports[`Storyshots Welcome MDX To Storybook 1`] = `
exports[`Storyshots Welcome MDX to Storybook 1`] = `
<Main>
<Title>
Welcome to storybook
@ -170,7 +170,7 @@ exports[`Storyshots Welcome MDX To Storybook 1`] = `
</Main>
`;
exports[`Storyshots Welcome To Storybook 1`] = `
exports[`Storyshots Welcome to Storybook 1`] = `
<Main>
<Title>
Welcome to storybook

View File

@ -45,13 +45,33 @@ exports[`Storyshots Another Button with text 1`] = `
</button>
`;
exports[`Storyshots Async With Timeout 1`] = `
exports[`Storyshots Async with 5ms timeout simulating async operation 1`] = `
<h1>
</h1>
`;
exports[`Storyshots Button With Some Emoji 1`] = `
exports[`Storyshots Button With Text 1`] = `
<button
onClick={[Function]}
style={
Object {
"backgroundColor": "#FFFFFF",
"border": "1px solid #eee",
"borderRadius": 3,
"cursor": "pointer",
"fontSize": 15,
"margin": 10,
"padding": "3px 10px",
}
}
type="button"
>
Hello Button
</button>
`;
exports[`Storyshots Button with some emoji 1`] = `
<button
onClick={[Function]}
style={
@ -76,27 +96,7 @@ exports[`Storyshots Button With Some Emoji 1`] = `
</button>
`;
exports[`Storyshots Button With Text 1`] = `
<button
onClick={[Function]}
style={
Object {
"backgroundColor": "#FFFFFF",
"border": "1px solid #eee",
"borderRadius": 3,
"cursor": "pointer",
"fontSize": 15,
"margin": 10,
"padding": "3px 10px",
}
}
type="button"
>
Hello Button
</button>
`;
exports[`Storyshots Welcome MDX To Storybook 1`] = `
exports[`Storyshots Welcome MDX to Storybook 1`] = `
<Main>
<Title>
Welcome to storybook
@ -170,7 +170,7 @@ exports[`Storyshots Welcome MDX To Storybook 1`] = `
</Main>
`;
exports[`Storyshots Welcome To Storybook 1`] = `
exports[`Storyshots Welcome to Storybook 1`] = `
<Main>
<Title>
Welcome to storybook

View File

@ -45,13 +45,33 @@ exports[`Storyshots Another Button with text 1`] = `
</button>
`;
exports[`Storyshots Async With Timeout 1`] = `
exports[`Storyshots Async with 5ms timeout simulating async operation 1`] = `
<h1>
</h1>
`;
exports[`Storyshots Button With Some Emoji 1`] = `
exports[`Storyshots Button With Text 1`] = `
<button
onClick={[Function]}
style={
Object {
"backgroundColor": "#FFFFFF",
"border": "1px solid #eee",
"borderRadius": 3,
"cursor": "pointer",
"fontSize": 15,
"margin": 10,
"padding": "3px 10px",
}
}
type="button"
>
Hello Button
</button>
`;
exports[`Storyshots Button with some emoji 1`] = `
<button
onClick={[Function]}
style={
@ -76,27 +96,7 @@ exports[`Storyshots Button With Some Emoji 1`] = `
</button>
`;
exports[`Storyshots Button With Text 1`] = `
<button
onClick={[Function]}
style={
Object {
"backgroundColor": "#FFFFFF",
"border": "1px solid #eee",
"borderRadius": 3,
"cursor": "pointer",
"fontSize": 15,
"margin": 10,
"padding": "3px 10px",
}
}
type="button"
>
Hello Button
</button>
`;
exports[`Storyshots Welcome MDX To Storybook 1`] = `
exports[`Storyshots Welcome MDX to Storybook 1`] = `
<article
style={
Object {
@ -270,7 +270,7 @@ exports[`Storyshots Welcome MDX To Storybook 1`] = `
</article>
`;
exports[`Storyshots Welcome To Storybook 1`] = `
exports[`Storyshots Welcome to Storybook 1`] = `
<article
style={
Object {

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Async With Timeout 1`] = `
exports[`Storyshots Async with 5ms timeout simulating async operation 1`] = `
<AsyncTestComponent>
<h1>
THIS IS SO DONE

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Async With Timeout 1`] = `
exports[`Storyshots Async with 5ms timeout simulating async operation 1`] = `
<h1>
</h1>

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Async With Timeout 1`] = `
exports[`Storyshots Async with 5ms timeout simulating async operation 1`] = `
<h1>
</h1>

Some files were not shown because too many files have changed in this diff Show More