From f120f6a91b45eef6cad5dfc05f2f14918766adb9 Mon Sep 17 00:00:00 2001 From: Prashant Palikhe Date: Sat, 1 Oct 2022 10:29:05 +0200 Subject: [PATCH] fix(vue2): enable final args as props to the story component --- .../src/stories/components/button.stories.js | 16 +++++++++++++- code/renderers/vue/src/decorateStory.ts | 13 ++++++++--- code/renderers/vue/src/render.ts | 22 ++++++++++++++----- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/code/examples/vue-kitchen-sink/src/stories/components/button.stories.js b/code/examples/vue-kitchen-sink/src/stories/components/button.stories.js index fee26cad5fd..e071f12d20d 100644 --- a/code/examples/vue-kitchen-sink/src/stories/components/button.stories.js +++ b/code/examples/vue-kitchen-sink/src/stories/components/button.stories.js @@ -1,3 +1,4 @@ +import { within, userEvent } from '@storybook/testing-library'; import MyButton from '../Button.vue'; export default { @@ -11,7 +12,8 @@ export default { const Template = (args, { argTypes }) => ({ props: Object.keys(argTypes), components: { MyButton }, - template: '{{label}}', + template: ` + {{label}}`, }); export const Rounded = Template.bind({}); @@ -20,6 +22,18 @@ Rounded.args = { color: '#f00', label: 'A Button with rounded edges', }; +Rounded.decorators = [ + (storyFn, context) => { + return storyFn({ ...context, args: { ...context.args, label: 'Overridden args' } }); + }, + () => ({ + template: '
', + }), +]; +Rounded.play = async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click(canvas.getByRole('button')); +}; export const Square = Template.bind({}); Square.args = { diff --git a/code/renderers/vue/src/decorateStory.ts b/code/renderers/vue/src/decorateStory.ts index 9ef7a2d34be..b8037067047 100644 --- a/code/renderers/vue/src/decorateStory.ts +++ b/code/renderers/vue/src/decorateStory.ts @@ -10,7 +10,8 @@ export const WRAPS = 'STORYBOOK_WRAPS'; function prepare( rawStory: StoryFnVueReturnType, - innerStory?: VueConstructor + innerStory?: VueConstructor, + context?: StoryContext ): VueConstructor | null { let story: ComponentOptions | VueConstructor; @@ -38,7 +39,11 @@ function prepare( // @ts-expect-error // https://github.com/storybookjs/storybook/pull/7578#discussion_r307985279 [WRAPS]: story, // @ts-expect-error // https://github.com/storybookjs/storybook/pull/7578#discussion_r307984824 - [VALUES]: { ...(innerStory ? innerStory.options[VALUES] : {}), ...extractProps(story) }, + [VALUES]: { + ...(innerStory ? innerStory.options[VALUES] : {}), + ...extractProps(story), + ...(context?.args || {}), + }, functional: true, render(h, { data, parent, children }) { return h( @@ -77,6 +82,8 @@ export function decorateStory( return prepare(decoratedStory, story as any); }, - (context) => prepare(storyFn(context)) + (context) => { + return prepare(storyFn(context), null, context); + } ); } diff --git a/code/renderers/vue/src/render.ts b/code/renderers/vue/src/render.ts index 22fec5d8bdb..a8c0fb91cb9 100644 --- a/code/renderers/vue/src/render.ts +++ b/code/renderers/vue/src/render.ts @@ -38,8 +38,7 @@ const getRoot = (domElement: Element): Instance => { }, render(h) { map.set(domElement, instance); - const children = this[COMPONENT] ? [h(this[COMPONENT])] : undefined; - return h('div', { attrs: { id: 'storybook-root' } }, children); + return this[COMPONENT] ? [h(this[COMPONENT])] : undefined; }, }); @@ -84,7 +83,6 @@ export function renderToDOM( title, name, storyFn, - storyContext: { args }, showMain, showError, showException, @@ -96,6 +94,20 @@ export function renderToDOM( Vue.config.errorHandler = showException; const element = storyFn(); + let mountTarget: Element; + + // Vue2 mount always replaces the mount target with Vue-generated DOM. + // https://v2.vuejs.org/v2/api/#el:~:text=replaced%20with%20Vue%2Dgenerated%20DOM + // We cannot mount to the domElement directly, because it would be replaced. That would + // break the references to the domElement like canvasElement used in the play function. + // Instead, we mount to a child element of the domElement, creating one if necessary. + if (domElement.hasChildNodes()) { + mountTarget = domElement.firstElementChild; + } else { + mountTarget = document.createElement('div'); + domElement.appendChild(mountTarget); + } + if (!element) { showError({ title: `Expecting a Vue component from the story: "${name}" of "${title}".`, @@ -113,10 +125,10 @@ export function renderToDOM( } // @ts-expect-error https://github.com/storybookjs/storrybook/pull/7578#discussion_r307986139 - root[VALUES] = { ...element.options[VALUES], ...args }; + root[VALUES] = { ...element.options[VALUES] }; if (!map.has(domElement)) { - root.$mount(domElement); + root.$mount(mountTarget); } showMain();