Skip to content

Commit

Permalink
feat: implement transform of simple cases
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiasdiez committed Sep 28, 2022
1 parent b14378c commit 3a8fed2
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 14 deletions.
8 changes: 5 additions & 3 deletions examples/vite/src/components/Button.stories.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<template>
<Story>
test
</Story>
<Stories>
<Story title="Primary">
<span>test</span>
</Story>
</Stories>
</template>
10 changes: 10 additions & 0 deletions examples/vite/src/components/MutlipleStories.stories.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<template>
<Stories>
<Story title="Primary">
hello
</Story>
<Story title="Secondary">
world
</Story>
</Stories>
</template>
90 changes: 82 additions & 8 deletions src/core/transform.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { compileTemplate, parse } from 'vue/compiler-sfc'
import type { ElementNode } from '@vue/compiler-core'

/**
* Transforms a vue single-file-component into Storybook's Component Story Format (CSF).
*/
export function transform(_code: string) {
export function transform(code: string) {
const { descriptor } = parse(code)
if (descriptor.template === null)
throw new Error('No template found in SFC')

return transformTemplate(descriptor.template.content)

/*
return `
import MyButton from './Button.vue';
import { userEvent, within } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
export default {
title: 'Example/ButtonTest',
component: MyButton,
Expand All @@ -18,15 +28,15 @@ export function transform(_code: string) {
onClick: {},
},
};
const Template = (args) => ({
components: { MyButton },
setup() {
return { args };
},
template: '<my-button v-bind="args" />',
});
export const Primary = Template.bind({});
Primary.args = {
primary: true,
Expand All @@ -38,22 +48,86 @@ export function transform(_code: string) {
await userEvent.click(button);
await expect(args.onClick).toHaveBeenCalled();
};
export const Secondary = Template.bind({});
Secondary.args = {
label: 'Button',
};
export const Large = Template.bind({});
Large.args = {
size: 'large',
label: 'Button',
};
export const Small = Template.bind({});
Small.args = {
size: 'small',
label: 'Button',
};
};
`
*/
}

function transformTemplate(content: string) {
const template = compileTemplate({
source: content,
filename: 'test.vue',
id: 'test',
/* compilerOptions: {
nodeTransforms: [extractTitle, replaceStoryNode],
}, */
})

const roots = template.ast?.children ?? []
if (roots.length !== 1)
throw new Error('Expected exactly one <Stories> element as root.')

const root = roots[0]
if (root.type !== 1 || root.tag !== 'Stories')
throw new Error('Expected root to be a <Stories> element.')

let result = generateDefaultImport(root)
for (const story of root.children ?? []) {
if (story.type !== 1 || story.tag !== 'Story')
continue

result += generateStoryImport(story)
}
return result
}

function generateDefaultImport(root: ElementNode) {
const title = extractTitle(root)
return `export default {
${title ? `title: '${title}',` : ''}
//component: MyComponent,
//decorators: [ ... ],
//parameters: { ... }
}
`
}

function extractTitle(node: ElementNode) {
if (node.type === 1) {
const titleProp = node.props.find(prop => prop.name === 'title')
if (titleProp && titleProp.type === 6)
return titleProp.value?.content
}
}

function generateStoryImport(story: ElementNode) {
const title = extractTitle(story)
if (!title)
throw new Error('Story is missing a title')
const storyTemplate = parse(story.loc.source.replace(/<Story/, '<template').replace(/<\/Story>/, '</template>')).descriptor.template?.content
if (storyTemplate === undefined)
throw new Error('No template found in Story')

const { code } = compileTemplate({ source: storyTemplate.trim(), filename: 'test.vue', id: 'test' })
const renderFunction = code.replace('export function render', `function render${title}`)

return `
${renderFunction}
export const ${title} = render${title}`
}
75 changes: 72 additions & 3 deletions test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,76 @@
import { describe, expect, it } from 'vitest'
import { compileTemplate, parse } from 'vue/compiler-sfc'
import { transform } from '../src/core/transform'

describe('index', () => {
it('hi vitest', () => {
expect(1).toBe(1)
describe('transform', () => {
it('handles one simple story', () => {
const code = '<template><Stories><Story title="Primary">hello</Story></Stories></template>'
const result = transform(code)
expect(result).toMatchInlineSnapshot(`
"export default {
//component: MyComponent,
//decorators: [ ... ],
//parameters: { ... }
}
function renderPrimary(_ctx, _cache) {
return \\"hello\\"
}
export const Primary = renderPrimary"
`)
})
it('extracts title from Stories', () => {
const code = '<template><Stories title="test"><Story title="Primary">hello</Story></Stories></template>'
const result = transform(code)
expect(result).toMatchInlineSnapshot(`
"export default {
title: 'test',
//component: MyComponent,
//decorators: [ ... ],
//parameters: { ... }
}
function renderPrimary(_ctx, _cache) {
return \\"hello\\"
}
export const Primary = renderPrimary"
`)
})
it('throws error if story does not have a title', () => {
const code = '<template><Stories><Story>hello</Story></Stories></template>'
expect(() => transform(code)).toThrowErrorMatchingInlineSnapshot('"Story is missing a title"')
})
it('handles multiple stories', () => {
const code = `
<template>
<Stories>
<Story title="Primary">hello</Story>
<Story title="Secondary">world</Story>
</Stories>
</template>`
const result = transform(code)
expect(result).toMatchInlineSnapshot(`
"export default {
//component: MyComponent,
//decorators: [ ... ],
//parameters: { ... }
}
function renderPrimary(_ctx, _cache) {
return \\"hello\\"
}
export const Primary = renderPrimary
function renderSecondary(_ctx, _cache) {
return \\"world\\"
}
export const Secondary = renderSecondary"
`)
},
)
})

0 comments on commit 3a8fed2

Please sign in to comment.