-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
224 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { Args } from '@storybook/api'; | ||
import { generateSvelteSource } from './sourceDecorator'; | ||
|
||
expect.addSnapshotSerializer({ | ||
print: (val: any) => val, | ||
test: (val) => typeof val === 'string', | ||
}); | ||
|
||
function generateForArgs(args: Args, slotProperty: string = null) { | ||
return generateSvelteSource({ name: 'Component' }, args, {}, slotProperty); | ||
} | ||
|
||
describe('generateSvelteSource', () => { | ||
test('boolean true', () => { | ||
expect(generateForArgs({ bool: true })).toMatchInlineSnapshot(`<Component bool/>`); | ||
}); | ||
test('boolean false', () => { | ||
expect(generateForArgs({ bool: false })).toMatchInlineSnapshot(`<Component bool={false}/>`); | ||
}); | ||
test('null property', () => { | ||
expect(generateForArgs({ propnull: null })).toMatchInlineSnapshot(`<Component />`); | ||
}); | ||
test('string property', () => { | ||
expect(generateForArgs({ str: 'mystr' })).toMatchInlineSnapshot(`<Component str="mystr"/>`); | ||
}); | ||
test('number property', () => { | ||
expect(generateForArgs({ count: 42 })).toMatchInlineSnapshot(`<Component count={42}/>`); | ||
}); | ||
test('object property', () => { | ||
expect(generateForArgs({ obj: { x: true } })).toMatchInlineSnapshot( | ||
`<Component obj={{"x":true}}/>` | ||
); | ||
}); | ||
test('multiple properties', () => { | ||
expect(generateForArgs({ a: 1, b: 2 })).toMatchInlineSnapshot(`<Component a={1} b={2}/>`); | ||
}); | ||
test('slot property', () => { | ||
expect(generateForArgs({ content: 'xyz', myProp: 'abc' }, 'content')).toMatchInlineSnapshot(` | ||
<Component myProp="abc"> | ||
xyz | ||
</Component> | ||
`); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import { addons, StoryContext } from '@storybook/addons'; | ||
import { ArgTypes, Args } from '@storybook/api'; | ||
|
||
import { SourceType, SNIPPET_RENDERED } from '../../shared'; | ||
|
||
/** | ||
* Check if the sourcecode should be generated. | ||
* | ||
* @param context StoryContext | ||
*/ | ||
const skipSourceRender = (context: StoryContext) => { | ||
const sourceParams = context?.parameters.docs?.source; | ||
const isArgsStory = context?.parameters.__isArgsStory; | ||
|
||
// always render if the user forces it | ||
if (sourceParams?.type === SourceType.DYNAMIC) { | ||
return false; | ||
} | ||
|
||
// never render if the user is forcing the block to render code, or | ||
// if the user provides code, or if it's not an args story. | ||
return !isArgsStory || sourceParams?.code || sourceParams?.type === SourceType.CODE; | ||
}; | ||
|
||
/** | ||
* Transform a key/value to a svelte declaration as string. | ||
* | ||
* Default values are ommited | ||
* | ||
* @param key Key | ||
* @param value Value | ||
* @param argTypes Component ArgTypes | ||
*/ | ||
function toSvelteProperty(key: string, value: any, argTypes: ArgTypes): string { | ||
if (value === undefined || value === null) { | ||
return null; | ||
} | ||
|
||
// default value ? | ||
if (argTypes[key] && argTypes[key].defaultValue === value) { | ||
return null; | ||
} | ||
|
||
if (value === true) { | ||
return key; | ||
} | ||
|
||
if (typeof value === 'string') { | ||
return `${key}=${JSON.stringify(value)}`; | ||
} | ||
|
||
return `${key}={${JSON.stringify(value)}}`; | ||
} | ||
|
||
/** | ||
* Extract a component name. | ||
* | ||
* @param component Component | ||
*/ | ||
function getComponentName(component: any): string { | ||
const { __docgen = {} } = component; | ||
let { name } = __docgen; | ||
|
||
if (!name) { | ||
return component.name; | ||
} | ||
|
||
if (name.endsWith('.svelte')) { | ||
name = name.substring(0, name.length - 7); | ||
} | ||
return name; | ||
} | ||
|
||
/** | ||
* Generate a svelte template. | ||
* | ||
* @param component Component | ||
* @param args Args | ||
* @param argTypes ArgTypes | ||
* @param slotProperty Property used to simulate a slot | ||
*/ | ||
export function generateSvelteSource( | ||
component: any, | ||
args: Args, | ||
argTypes: ArgTypes, | ||
slotProperty: string | ||
): string { | ||
const name = getComponentName(component); | ||
|
||
if (!name) { | ||
return null; | ||
} | ||
|
||
const props = Object.entries(args) | ||
.filter(([k]) => k !== slotProperty) | ||
.map(([k, v]) => toSvelteProperty(k, v, argTypes)) | ||
.filter((p) => p) | ||
.join(' '); | ||
|
||
const slotValue = slotProperty ? args[slotProperty] : null; | ||
|
||
if (slotValue) { | ||
return `<${name} ${props}>\n ${slotValue}\n</${name}>`; | ||
} | ||
|
||
return `<${name} ${props}/>`; | ||
} | ||
|
||
/** | ||
* Check if the story component is a wrapper to the real component. | ||
* | ||
* A component can be annoted with @wrapper to indicate that | ||
* it's just a wrapper for the real tested component. If it's the case | ||
* then the code generated references the real component, not the wrapper. | ||
* | ||
* moreover, a wrapper can annotate a property with @slot : this property | ||
* is then assumed to be an alias to the default slot. | ||
* | ||
* @param component Component | ||
*/ | ||
function getWrapperProperties(component: any) { | ||
const { __docgen } = component; | ||
if (!__docgen) { | ||
return { wrapper: false }; | ||
} | ||
|
||
// the component should be declared as a wrapper | ||
if (!__docgen.keywords.find((kw: any) => kw.name === 'wrapper')) { | ||
return { wrapper: false }; | ||
} | ||
|
||
const slotProp = __docgen.data.find((prop: any) => | ||
prop.keywords.find((kw: any) => kw.name === 'slot') | ||
); | ||
return { wrapper: true, slotProperty: slotProp?.name as string }; | ||
} | ||
|
||
/** | ||
* Svelte source decorator. | ||
* @param storyFn Fn | ||
* @param context StoryContext | ||
*/ | ||
export const sourceDecorator = (storyFn: any, context: StoryContext) => { | ||
const story = storyFn(); | ||
|
||
if (skipSourceRender(context)) { | ||
return story; | ||
} | ||
|
||
const channel = addons.getChannel(); | ||
|
||
const { parameters = {}, args = {} } = context || {}; | ||
let { Component: component = {} } = story; | ||
|
||
const { wrapper, slotProperty } = getWrapperProperties(component); | ||
if (wrapper) { | ||
component = parameters.component; | ||
} | ||
|
||
const source = generateSvelteSource(component, args, context?.argTypes, slotProperty); | ||
|
||
if (source) { | ||
channel.emit(SNIPPET_RENDERED, (context || {}).id, source); | ||
} | ||
|
||
return story; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters