mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-19 05:02:40 +08:00
Merge branch 'next' into core/magic-entry
This commit is contained in:
commit
54cddeb789
@ -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',
|
||||
|
19
CHANGELOG.md
19
CHANGELOG.md
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
22
MIGRATION.md
22
MIGRATION.md
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -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', () => {
|
||||
|
@ -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;
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
@ -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 };
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
39
addons/docs/postinstall/presets.js
Normal file
39
addons/docs/postinstall/presets.js
Normal 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' });
|
||||
}
|
@ -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;
|
||||
|
||||
|
18
addons/docs/src/blocks/DocsPage.test.ts
Normal file
18
addons/docs/src/blocks/DocsPage.test.ts
Normal 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');
|
||||
});
|
||||
});
|
@ -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} />
|
||||
)}
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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);
|
||||
|
@ -1 +1 @@
|
||||
export * from '../../lib/docgenUtils';
|
||||
export * from '../../lib/docgen';
|
||||
|
@ -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: {
|
||||
|
@ -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'),
|
||||
|
199
addons/docs/src/frameworks/react/inspection/acornParser.test.ts
Normal file
199
addons/docs/src/frameworks/react/inspection/acornParser.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
211
addons/docs/src/frameworks/react/inspection/acornParser.ts
Normal file
211
addons/docs/src/frameworks/react/inspection/acornParser.ts
Normal 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;
|
||||
}
|
14
addons/docs/src/frameworks/react/inspection/inspectValue.ts
Normal file
14
addons/docs/src/frameworks/react/inspection/inspectValue.ts
Normal 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 } };
|
||||
}
|
79
addons/docs/src/frameworks/react/inspection/types.ts
Normal file
79
addons/docs/src/frameworks/react/inspection/types.ts
Normal 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;
|
||||
}
|
6
addons/docs/src/frameworks/react/propTypes/captions.ts
Normal file
6
addons/docs/src/frameworks/react/propTypes/captions.ts
Normal 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';
|
115
addons/docs/src/frameworks/react/propTypes/createDefaultValue.ts
Normal file
115
addons/docs/src/frameworks/react/propTypes/createDefaultValue.ts
Normal 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;
|
||||
}
|
||||
}
|
366
addons/docs/src/frameworks/react/propTypes/createType.ts
Normal file
366
addons/docs/src/frameworks/react/propTypes/createType.ts
Normal 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;
|
||||
}
|
||||
}
|
25
addons/docs/src/frameworks/react/propTypes/generateCode.ts
Normal file
25
addons/docs/src/frameworks/react/propTypes/generateCode.ts
Normal 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);
|
||||
}
|
@ -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');
|
||||
});
|
@ -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(' ');
|
||||
}
|
673
addons/docs/src/frameworks/react/propTypes/handleProp.test.ts
Normal file
673
addons/docs/src/frameworks/react/propTypes/handleProp.test.ts
Normal 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, ''));
|
||||
});
|
||||
});
|
||||
});
|
24
addons/docs/src/frameworks/react/propTypes/handleProp.ts
Normal file
24
addons/docs/src/frameworks/react/propTypes/handleProp.ts
Normal 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;
|
||||
}
|
5
addons/docs/src/frameworks/react/propTypes/isHtmlTag.ts
Normal file
5
addons/docs/src/frameworks/react/propTypes/isHtmlTag.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import htmlTags from 'html-tags';
|
||||
|
||||
export function isHtmlTag(tagName: string): boolean {
|
||||
return htmlTags.includes(tagName.toLowerCase());
|
||||
}
|
@ -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: {
|
||||
|
@ -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 };
|
||||
};
|
||||
|
@ -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 },
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
23
addons/docs/src/lib/docgen/createDefaultValue.ts
Normal file
23
addons/docs/src/lib/docgen/createDefaultValue.ts
Normal 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;
|
||||
}
|
99
addons/docs/src/lib/docgen/createPropDef.ts
Normal file
99
addons/docs/src/lib/docgen/createPropDef.ts
Normal 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;
|
||||
}
|
||||
};
|
155
addons/docs/src/lib/docgen/extractDocgenProps.test.ts
Normal file
155
addons/docs/src/lib/docgen/extractDocgenProps.test.ts
Normal 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();
|
||||
});
|
||||
});
|
77
addons/docs/src/lib/docgen/extractDocgenProps.ts
Normal file
77
addons/docs/src/lib/docgen/extractDocgenProps.ts
Normal 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;
|
||||
}
|
3
addons/docs/src/lib/docgen/index.ts
Normal file
3
addons/docs/src/lib/docgen/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './types';
|
||||
export * from './utils';
|
||||
export * from './extractDocgenProps';
|
44
addons/docs/src/lib/docgen/types.ts
Normal file
44
addons/docs/src/lib/docgen/types.ts
Normal 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',
|
||||
}
|
29
addons/docs/src/lib/docgen/utils.ts
Normal file
29
addons/docs/src/lib/docgen/utils.ts
Normal 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);
|
@ -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);
|
@ -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;
|
||||
}
|
344
addons/docs/src/lib/jsdocParser.test.ts
Normal file
344
addons/docs/src/lib/jsdocParser.test.ts
Normal 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');
|
||||
});
|
||||
});
|
234
addons/docs/src/lib/jsdocParser.ts
Normal file
234
addons/docs/src/lib/jsdocParser.ts
Normal 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;
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
@ -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];
|
||||
};
|
@ -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;
|
||||
"
|
||||
`;
|
8
addons/docs/src/mdx/__testfixtures__/component-id.mdx
Normal file
8
addons/docs/src/mdx/__testfixtures__/component-id.mdx
Normal 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>
|
@ -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;
|
||||
"
|
||||
`;
|
@ -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;
|
||||
"
|
||||
`;
|
@ -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;
|
||||
"
|
||||
`;
|
@ -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;
|
||||
"
|
||||
`;
|
@ -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;
|
||||
"
|
||||
`;
|
@ -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;
|
||||
"
|
||||
`;
|
@ -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;
|
||||
"
|
||||
`;
|
@ -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;
|
||||
"
|
||||
`;
|
@ -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;
|
||||
"
|
||||
`;
|
@ -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;
|
||||
"
|
||||
`;
|
@ -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;
|
||||
"
|
||||
`;
|
@ -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;
|
||||
"
|
||||
`;
|
@ -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;
|
||||
"
|
||||
`;
|
47
addons/docs/src/mdx/__testfixtures__/vanilla.output.snapshot
Normal file
47
addons/docs/src/mdx/__testfixtures__/vanilla.output.snapshot
Normal 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;
|
||||
"
|
||||
`;
|
@ -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,
|
||||
};
|
||||
|
@ -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'))
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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 (
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": "*",
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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}
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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]}
|
||||
>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user