From 48f343dff71ef705467ea085fe68e3c04bef473f Mon Sep 17 00:00:00 2001 From: chakAs3 Date: Fri, 9 Jun 2023 10:19:49 +0400 Subject: [PATCH 01/10] revert to chaks vue3 impl --- code/renderers/vue3/src/decorateStory.ts | 39 ++++++++++++++++++------ code/renderers/vue3/src/render.ts | 32 ++++++++++++++----- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/code/renderers/vue3/src/decorateStory.ts b/code/renderers/vue3/src/decorateStory.ts index 0835bad6da18..6ac1f278fb8f 100644 --- a/code/renderers/vue3/src/decorateStory.ts +++ b/code/renderers/vue3/src/decorateStory.ts @@ -1,8 +1,10 @@ -import type { Component, ComponentOptions, ConcreteComponent } from 'vue'; -import { h } from 'vue'; -import type { DecoratorFunction, LegacyStoryFn, StoryContext } from '@storybook/types'; +import type { ConcreteComponent, Component, ComponentOptions } from 'vue'; +import { reactive, h } from 'vue'; +import type { DecoratorFunction, StoryContext, LegacyStoryFn, Args } from '@storybook/types'; import { sanitizeStoryContextUpdate } from '@storybook/preview-api'; + import type { VueRenderer } from './types'; +import { updateArgs } from './render'; /* This normalizes a functional component into a render method in ComponentOptions. @@ -20,6 +22,7 @@ function prepare( innerStory?: ConcreteComponent ): Component | null { const story = rawStory as ComponentOptions; + if (story === null) { return null; } @@ -31,7 +34,6 @@ function prepare( components: { ...(story.components || {}), story: innerStory }, }; } - return { render() { return h(story); @@ -43,27 +45,44 @@ export function decorateStory( storyFn: LegacyStoryFn, decorators: DecoratorFunction[] ): LegacyStoryFn { + const updatedArgs: Args = reactive({}); + return decorators.reduce( (decorated: LegacyStoryFn, decorator) => (context: StoryContext) => { let story: VueRenderer['storyResult'] | undefined; const decoratedStory: VueRenderer['storyResult'] = decorator((update) => { - const sanitizedUpdate = sanitizeStoryContextUpdate(update); - // update the args in a reactive way - if (update) sanitizedUpdate.args = Object.assign(context.args, sanitizedUpdate.args); - story = decorated({ ...context, ...sanitizedUpdate }); + story = decorated({ + ...context, + ...sanitizeStoryContextUpdate(update), + }); + + if ( + update && + update.args && + Object.keys(update).length === 1 && + Object.keys(updateArgs).length === 0 + ) { + updateArgs(updatedArgs, update.args); + } return story; }, context); + updateArgs(context.args, updatedArgs); + if (!story) story = decorated(context); if (decoratedStory === story) { return story; } - const innerStory = () => h(story!); + const innerStory = () => h(story!, context.args); return prepare(decoratedStory, innerStory) as VueRenderer['storyResult']; }, - (context) => prepare(storyFn(context)) as LegacyStoryFn + (context) => { + const story = storyFn(context); + story.inheritAttrs ??= context.parameters.inheritAttrs ?? false; + return prepare(story) as LegacyStoryFn; + } ); } diff --git a/code/renderers/vue3/src/render.ts b/code/renderers/vue3/src/render.ts index 552438220600..0c3102957a9c 100644 --- a/code/renderers/vue3/src/render.ts +++ b/code/renderers/vue3/src/render.ts @@ -1,10 +1,18 @@ /* eslint-disable no-param-reassign */ -import type { App } from 'vue'; +import type { App, ConcreteComponent } from 'vue'; import { createApp, h, reactive, isVNode, isReactive } from 'vue'; -import type { ArgsStoryFn, RenderContext } from '@storybook/types'; +import type { RenderContext, ArgsStoryFn } from '@storybook/types'; import type { Args, StoryContext } from '@storybook/csf'; -import type { StoryFnVueReturnType, StoryID, VueRenderer } from './types'; +import type { VueRenderer, StoryFnVueReturnType, StoryID } from './types'; + +const slotsMap = new Map< + StoryID, + { + component?: Omit, 'props'>; + reactiveSlots?: Args; + } +>(); export const render: ArgsStoryFn = (props, context) => { const { id, component: Component } = context; @@ -14,7 +22,7 @@ export const render: ArgsStoryFn = (props, context) => { ); } - return () => h(Component, props, generateSlots(context)); + return h(Component, props, createOrUpdateSlots(context)); }; // set of setup functions that will be called when story is created @@ -74,9 +82,7 @@ export function renderToCanvas( map.set(canvasElement, appState); return () => { - // not passing args here as props - // treat the rootElement as a component without props - return h(rootElement); + return h(rootElement, appState.reactiveArgs); }; }, }); @@ -151,3 +157,15 @@ function teardown( storybookApp?.unmount(); if (map.has(canvasElement)) map.delete(canvasElement); } + +function createOrUpdateSlots(context: StoryContext) { + const { id: storyID, component } = context; + const slots = generateSlots(context); + if (slotsMap.has(storyID)) { + const app = slotsMap.get(storyID); + if (app?.reactiveSlots) updateArgs(app.reactiveSlots, slots); + return app?.reactiveSlots; + } + slotsMap.set(storyID, { component, reactiveSlots: slots }); + return slots; +} From 7d6626d0be1a81548918c90b584e209e5a9b3ff3 Mon Sep 17 00:00:00 2001 From: chakAs3 Date: Fri, 9 Jun 2023 10:23:21 +0400 Subject: [PATCH 02/10] revert back test stories ( no need to spread ) --- .../preview-api/template/stories/argMapping.stories.ts | 2 +- code/lib/preview-api/template/stories/argTypes.stories.ts | 2 +- code/lib/preview-api/template/stories/args.stories.ts | 3 +-- .../preview-api/template/stories/decorators.stories.ts | 8 +++++--- code/lib/preview-api/template/stories/hooks.stories.ts | 2 ++ 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/code/lib/preview-api/template/stories/argMapping.stories.ts b/code/lib/preview-api/template/stories/argMapping.stories.ts index d5fa145700a7..655647660c9d 100644 --- a/code/lib/preview-api/template/stories/argMapping.stories.ts +++ b/code/lib/preview-api/template/stories/argMapping.stories.ts @@ -22,7 +22,7 @@ export default { decorators: [ (storyFn: PartialStoryFn, context: StoryContext) => { return storyFn({ - args: { object: { ...context.args } }, + args: { object: context.args }, }); }, ], diff --git a/code/lib/preview-api/template/stories/argTypes.stories.ts b/code/lib/preview-api/template/stories/argTypes.stories.ts index e0d2115d10ae..93b9540ebb40 100644 --- a/code/lib/preview-api/template/stories/argTypes.stories.ts +++ b/code/lib/preview-api/template/stories/argTypes.stories.ts @@ -8,7 +8,7 @@ export default { // Compose all the argTypes into `object`, so the pre component only needs a single prop decorators: [ (storyFn: PartialStoryFn, context: StoryContext) => - storyFn({ args: { object: { ...context.argTypes } } }), + storyFn({ args: { object: context.argTypes } }), ], argTypes: { componentArg: { type: 'string' }, diff --git a/code/lib/preview-api/template/stories/args.stories.ts b/code/lib/preview-api/template/stories/args.stories.ts index aa9719c4bf2c..7c80ddb679ec 100644 --- a/code/lib/preview-api/template/stories/args.stories.ts +++ b/code/lib/preview-api/template/stories/args.stories.ts @@ -20,8 +20,7 @@ export default { decorators: [ (storyFn: PartialStoryFn, context: StoryContext) => { const { argNames } = context.parameters; - const args = { ...context.args }; - const object = argNames ? pick(args, argNames) : args; + const object = argNames ? pick(context.args, argNames) : context.args; return storyFn({ args: { object } }); }, ], diff --git a/code/lib/preview-api/template/stories/decorators.stories.ts b/code/lib/preview-api/template/stories/decorators.stories.ts index a36eb100a739..91964f5002d3 100644 --- a/code/lib/preview-api/template/stories/decorators.stories.ts +++ b/code/lib/preview-api/template/stories/decorators.stories.ts @@ -40,13 +40,15 @@ export const Hooks = { // decorator that uses hooks (storyFn: PartialStoryFn, context: StoryContext) => { useEffect(() => {}); + return storyFn({ args: { ...context.args, text: `story ${context.args['text']}` } }); }, // conditional decorator, runs before the above - (storyFn: PartialStoryFn, context: StoryContext) => - context.args.condition + (storyFn: PartialStoryFn, context: StoryContext) => { + return context.args.condition ? storyFn() - : (context.originalStoryFn as ArgsStoryFn)(context.args, context), + : (context.originalStoryFn as ArgsStoryFn)(context.unmappedArgs, context); + }, ], args: { text: 'text', diff --git a/code/lib/preview-api/template/stories/hooks.stories.ts b/code/lib/preview-api/template/stories/hooks.stories.ts index 15a4fe90a2ea..4f937686c18b 100644 --- a/code/lib/preview-api/template/stories/hooks.stories.ts +++ b/code/lib/preview-api/template/stories/hooks.stories.ts @@ -8,6 +8,8 @@ export default { }; export const UseState = { + // parameters: { inheritAttrs: true }, + args: { label: 'Clicked 0 times' }, decorators: [ (story: PartialStoryFn) => { const [count, setCount] = useState(0); From c950f970c724c6c98086f9d95c8273ee106e78d9 Mon Sep 17 00:00:00 2001 From: chakAs3 Date: Fri, 9 Jun 2023 11:39:36 +0400 Subject: [PATCH 03/10] use shallow reactive to avoid deep reactivity --- code/renderers/vue3/src/decorateStory.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/code/renderers/vue3/src/decorateStory.ts b/code/renderers/vue3/src/decorateStory.ts index 6ac1f278fb8f..09d67efb9b60 100644 --- a/code/renderers/vue3/src/decorateStory.ts +++ b/code/renderers/vue3/src/decorateStory.ts @@ -1,5 +1,5 @@ import type { ConcreteComponent, Component, ComponentOptions } from 'vue'; -import { reactive, h } from 'vue'; +import { shallowReactive, h } from 'vue'; import type { DecoratorFunction, StoryContext, LegacyStoryFn, Args } from '@storybook/types'; import { sanitizeStoryContextUpdate } from '@storybook/preview-api'; @@ -45,11 +45,10 @@ export function decorateStory( storyFn: LegacyStoryFn, decorators: DecoratorFunction[] ): LegacyStoryFn { - const updatedArgs: Args = reactive({}); - return decorators.reduce( (decorated: LegacyStoryFn, decorator) => (context: StoryContext) => { let story: VueRenderer['storyResult'] | undefined; + const updatedArgs = shallowReactive({}); const decoratedStory: VueRenderer['storyResult'] = decorator((update) => { story = decorated({ @@ -68,7 +67,7 @@ export function decorateStory( return story; }, context); - updateArgs(context.args, updatedArgs); + context.args = updatedArgs; if (!story) story = decorated(context); From 0bb078892af0e2dd1634c57ffe0272c09d083899 Mon Sep 17 00:00:00 2001 From: chakAs3 Date: Fri, 9 Jun 2023 12:33:59 +0400 Subject: [PATCH 04/10] updatedArgs reinitialisation in first reducer --- code/renderers/vue3/src/decorateStory.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/code/renderers/vue3/src/decorateStory.ts b/code/renderers/vue3/src/decorateStory.ts index 09d67efb9b60..1682717f7d8e 100644 --- a/code/renderers/vue3/src/decorateStory.ts +++ b/code/renderers/vue3/src/decorateStory.ts @@ -45,10 +45,11 @@ export function decorateStory( storyFn: LegacyStoryFn, decorators: DecoratorFunction[] ): LegacyStoryFn { + let updatedArgs = {}; + return decorators.reduce( (decorated: LegacyStoryFn, decorator) => (context: StoryContext) => { let story: VueRenderer['storyResult'] | undefined; - const updatedArgs = shallowReactive({}); const decoratedStory: VueRenderer['storyResult'] = decorator((update) => { story = decorated({ @@ -60,7 +61,7 @@ export function decorateStory( update && update.args && Object.keys(update).length === 1 && - Object.keys(updateArgs).length === 0 + Object.keys(updatedArgs).length === 0 ) { updateArgs(updatedArgs, update.args); } @@ -79,6 +80,7 @@ export function decorateStory( return prepare(decoratedStory, innerStory) as VueRenderer['storyResult']; }, (context) => { + updatedArgs = {}; const story = storyFn(context); story.inheritAttrs ??= context.parameters.inheritAttrs ?? false; return prepare(story) as LegacyStoryFn; From 29efc3dacd7348ca1fd591e3546ce4139be98405 Mon Sep 17 00:00:00 2001 From: chakAs3 Date: Fri, 9 Jun 2023 12:42:24 +0400 Subject: [PATCH 05/10] fix type checking --- code/renderers/vue3/src/decorateStory.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/renderers/vue3/src/decorateStory.ts b/code/renderers/vue3/src/decorateStory.ts index 1682717f7d8e..e33a24cf4698 100644 --- a/code/renderers/vue3/src/decorateStory.ts +++ b/code/renderers/vue3/src/decorateStory.ts @@ -1,6 +1,6 @@ import type { ConcreteComponent, Component, ComponentOptions } from 'vue'; -import { shallowReactive, h } from 'vue'; -import type { DecoratorFunction, StoryContext, LegacyStoryFn, Args } from '@storybook/types'; +import { h } from 'vue'; +import type { DecoratorFunction, StoryContext, LegacyStoryFn } from '@storybook/types'; import { sanitizeStoryContextUpdate } from '@storybook/preview-api'; import type { VueRenderer } from './types'; From 2d2f7eadfe8c2ec135c16e368d0636f9cb78d9be Mon Sep 17 00:00:00 2001 From: chakAs3 Date: Fri, 9 Jun 2023 13:34:24 +0400 Subject: [PATCH 06/10] fix playwright test runing issue --- code/package.json | 1 - code/yarn.lock | 1 - 2 files changed, 2 deletions(-) diff --git a/code/package.json b/code/package.json index 725a4333c133..0206fd8ccd62 100644 --- a/code/package.json +++ b/code/package.json @@ -246,7 +246,6 @@ "node-gyp": "^9.3.1", "nx": "16.2.1", "nx-cloud": "16.0.5", - "playwright": "^1.35.0", "playwright-core": "^1.35.0", "prettier": "2.8.0", "process": "^0.11.10", diff --git a/code/yarn.lock b/code/yarn.lock index 42040001bf2c..3a214672c3a9 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -7210,7 +7210,6 @@ __metadata: node-gyp: ^9.3.1 nx: 16.2.1 nx-cloud: 16.0.5 - playwright: ^1.35.0 playwright-core: ^1.35.0 prettier: 2.8.0 process: ^0.11.10 From ba1da92267f95507c21384d89744cd3110f7d632 Mon Sep 17 00:00:00 2001 From: chakAs3 Date: Fri, 9 Jun 2023 14:36:04 +0400 Subject: [PATCH 07/10] cleanup after test --- code/renderers/vue3/package.json | 1 + code/renderers/vue3/src/decorateStory.ts | 4 ++-- code/renderers/vue3/src/render.ts | 3 ++- code/yarn.lock | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/code/renderers/vue3/package.json b/code/renderers/vue3/package.json index ff5197cb8902..7bceafc8401c 100644 --- a/code/renderers/vue3/package.json +++ b/code/renderers/vue3/package.json @@ -66,6 +66,7 @@ "vue-tsc": "^1.2.0" }, "peerDependencies": { + "lodash": "*", "vue": "^3.0.0" }, "engines": { diff --git a/code/renderers/vue3/src/decorateStory.ts b/code/renderers/vue3/src/decorateStory.ts index e33a24cf4698..c3fb9c5ef9c3 100644 --- a/code/renderers/vue3/src/decorateStory.ts +++ b/code/renderers/vue3/src/decorateStory.ts @@ -68,7 +68,7 @@ export function decorateStory( return story; }, context); - context.args = updatedArgs; + updateArgs(context.args, updatedArgs); if (!story) story = decorated(context); @@ -82,7 +82,7 @@ export function decorateStory( (context) => { updatedArgs = {}; const story = storyFn(context); - story.inheritAttrs ??= context.parameters.inheritAttrs ?? false; + story.inheritAttrs ??= context.parameters.inheritAttrs ?? true; return prepare(story) as LegacyStoryFn; } ); diff --git a/code/renderers/vue3/src/render.ts b/code/renderers/vue3/src/render.ts index 0c3102957a9c..b3e6678f241b 100644 --- a/code/renderers/vue3/src/render.ts +++ b/code/renderers/vue3/src/render.ts @@ -4,6 +4,7 @@ import { createApp, h, reactive, isVNode, isReactive } from 'vue'; import type { RenderContext, ArgsStoryFn } from '@storybook/types'; import type { Args, StoryContext } from '@storybook/csf'; +import { cloneDeep } from 'lodash'; import type { VueRenderer, StoryFnVueReturnType, StoryID } from './types'; const slotsMap = new Map< @@ -139,7 +140,7 @@ export function updateArgs(reactiveArgs: Args, nextArgs: Args) { } }); // update currentArgs with nextArgs - Object.assign(currentArgs, nextArgs); + Object.assign(currentArgs, cloneDeep(nextArgs)); } /** diff --git a/code/yarn.lock b/code/yarn.lock index 3a214672c3a9..72d96c0a1bcd 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -7578,6 +7578,7 @@ __metadata: vue-component-type-helpers: ^1.6.5 vue-tsc: ^1.2.0 peerDependencies: + lodash: "*" vue: ^3.0.0 languageName: unknown linkType: soft From 4bc5b4d82cee889ebce61264b2c14a92a6a7f33d Mon Sep 17 00:00:00 2001 From: chakAs3 Date: Fri, 9 Jun 2023 18:07:47 +0400 Subject: [PATCH 08/10] simplify and improve visually the tests, --- ...CustomRenderFunctionalComponent.stories.ts | 4 +-- ...CustomRenderOptionsArgsFromData.stories.ts | 5 ++-- .../ReactiveDecorators.stories.ts | 24 ++++++++++------- .../Reactivity.vue | 26 ++++++++++++++++--- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/CustomRenderFunctionalComponent.stories.ts b/code/renderers/vue3/template/stories_vue3-vite-default-ts/CustomRenderFunctionalComponent.stories.ts index bf70bc34f074..4bd04879b2e7 100644 --- a/code/renderers/vue3/template/stories_vue3-vite-default-ts/CustomRenderFunctionalComponent.stories.ts +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/CustomRenderFunctionalComponent.stories.ts @@ -9,13 +9,13 @@ const meta = { // storybook render function is not a functional component. it returns a functional component or a component options render: (args) => { // create the slot contents as a functional components - const header = ({ title }: { title: string }) => h('h3', `${args.header} - Title: ${title}`); + const header = ({ title }: { title: string }) => h('h4', `${args.header} - Title: ${title}`); const defaultSlot = () => h('p', `${args.default}`); const footer = () => h('p', `${args.footer}`); // vue render function is a functional components return () => h('div', [ - `Custom render uses a functional component, and passes slots to the component:`, + h('h3', 'Custom render returns a functional component'), h(Reactivity, args, { header, default: defaultSlot, footer }), ]); }, diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/CustomRenderOptionsArgsFromData.stories.ts b/code/renderers/vue3/template/stories_vue3-vite-default-ts/CustomRenderOptionsArgsFromData.stories.ts index 9c904283d3bd..3e86a9dc6e19 100644 --- a/code/renderers/vue3/template/stories_vue3-vite-default-ts/CustomRenderOptionsArgsFromData.stories.ts +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/CustomRenderOptionsArgsFromData.stories.ts @@ -23,9 +23,10 @@ const meta = { components: { Reactivity, }, - template: `
Custom render uses options api and binds args to data: + template: `
+

Custom render returns options API Component

- + diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveDecorators.stories.ts b/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveDecorators.stories.ts index 143cd1784559..e6328439a02a 100644 --- a/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveDecorators.stories.ts +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/ReactiveDecorators.stories.ts @@ -13,10 +13,10 @@ const meta = { default: { control: { type: 'text' } }, }, args: { - label: 'If you see this then the label arg was not reactive.', - default: 'If you see this then the default slot was not reactive.', - header: 'If you see this, the header slot was not reactive.', // this can be useless if you have custom render function that overrides the slot - footer: 'If you see this, the footer slot was not reactive.', + label: 'initial label', + default: 'initial default slot.', + header: 'initial header slot', // this can be useless if you have custom render function that overrides the slot + footer: 'initial footer slot.', }, play: async ({ canvasElement, id, args }) => { const channel = (globalThis as any).__STORYBOOK_ADDONS_CHANNEL__; @@ -51,7 +51,9 @@ export const DecoratorFunctionalComponent: Story = { decorators: [ (storyFn, context) => { const story = storyFn(); - return () => h('div', [h('h2', ['Decorator not using args']), [h(story)]]); + return () => { + return h('div', { style: 'border: 5px solid red' }, h(story)); + }; }, ], }; @@ -60,8 +62,10 @@ export const DecoratorFunctionalComponentArgsFromContext: Story = { decorators: [ (storyFn, context) => { const story = storyFn(); - return () => - h('div', [h('h2', ['Decorator using args.label: ', context.args.label]), [h(story)]]); + + return () => { + return h('div', { style: 'border: 5px solid blue' }, h(story, context.args)); + }; }, ], }; @@ -70,7 +74,8 @@ export const DecoratorComponentOptions: Story = { decorators: [ (storyFn, context) => { return { - template: '

Decorator not using args

', + components: { story: storyFn() }, + template: `
`, }; }, ], @@ -80,8 +85,9 @@ export const DecoratorComponentOptionsArgsFromData: Story = { decorators: [ (storyFn, context) => { return { + components: { story: storyFn() }, data: () => ({ args: context.args }), - template: '

Decorator using args.label: {{args.label}}

', + template: `
`, }; }, ], diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/Reactivity.vue b/code/renderers/vue3/template/stories_vue3-vite-default-ts/Reactivity.vue index 89880cb5b6cd..7e7c0c3631af 100644 --- a/code/renderers/vue3/template/stories_vue3-vite-default-ts/Reactivity.vue +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/Reactivity.vue @@ -2,7 +2,7 @@ defineProps<{ label: string }>(); From 8c7a8d71f73c8828aa69abca58d5fd57c603d030 Mon Sep 17 00:00:00 2001 From: chakAs3 Date: Sat, 10 Jun 2023 05:02:31 +0400 Subject: [PATCH 09/10] refactory render & decorateStory for better read. --- code/renderers/vue3/src/decorateStory.ts | 79 ++++++++---------------- code/renderers/vue3/src/render.ts | 23 ++----- 2 files changed, 32 insertions(+), 70 deletions(-) diff --git a/code/renderers/vue3/src/decorateStory.ts b/code/renderers/vue3/src/decorateStory.ts index c3fb9c5ef9c3..a4545236bf13 100644 --- a/code/renderers/vue3/src/decorateStory.ts +++ b/code/renderers/vue3/src/decorateStory.ts @@ -4,7 +4,6 @@ import type { DecoratorFunction, StoryContext, LegacyStoryFn } from '@storybook/ import { sanitizeStoryContextUpdate } from '@storybook/preview-api'; import type { VueRenderer } from './types'; -import { updateArgs } from './render'; /* This normalizes a functional component into a render method in ComponentOptions. @@ -13,77 +12,51 @@ import { updateArgs } from './render'; method on the ComponentOptions so end-users don't need to specify a "thunk" as a decorator. */ -function normalizeFunctionalComponent(options: ConcreteComponent): ComponentOptions { - return typeof options === 'function' ? { render: options, name: options.name } : options; -} - function prepare( rawStory: VueRenderer['storyResult'], innerStory?: ConcreteComponent ): Component | null { - const story = rawStory as ComponentOptions; + const story: Component = rawStory; - if (story === null) { - return null; + if (!story || typeof story === 'function') { + return story; } - if (typeof story === 'function') return story; // we don't need to wrap a functional component nor to convert it to a component options + if (innerStory) { return { // Normalize so we can always spread an object - ...normalizeFunctionalComponent(story), + ...story, // we don't to normalize the story if it's a functional component as it's already returned components: { ...(story.components || {}), story: innerStory }, }; } - return { - render() { - return h(story); - }, - }; + + const render = () => h(story); + return { render }; } export function decorateStory( storyFn: LegacyStoryFn, decorators: DecoratorFunction[] ): LegacyStoryFn { - let updatedArgs = {}; - - return decorators.reduce( - (decorated: LegacyStoryFn, decorator) => (context: StoryContext) => { - let story: VueRenderer['storyResult'] | undefined; - - const decoratedStory: VueRenderer['storyResult'] = decorator((update) => { - story = decorated({ - ...context, - ...sanitizeStoryContextUpdate(update), - }); - - if ( - update && - update.args && - Object.keys(update).length === 1 && - Object.keys(updatedArgs).length === 0 - ) { - updateArgs(updatedArgs, update.args); - } - return story; + const decoratedStoryFn = decorators.reduce((decoratedFn, decorator) => { + const decoratedFunc = (context: StoryContext) => + decorator((update) => { + const mergedContext = { ...context, ...sanitizeStoryContextUpdate(update) }; + context.args = mergedContext.args; + const storyResult = decoratedFn(context); + return storyResult; }, context); - updateArgs(context.args, updatedArgs); - - if (!story) story = decorated(context); - - if (decoratedStory === story) { - return story; - } + return (context: StoryContext) => { + const story = decoratedFunc(context); + const innerStory = () => h(story, context.args); + return prepare(story, innerStory) as VueRenderer['storyResult']; + }; + }, storyFn); - const innerStory = () => h(story!, context.args); - return prepare(decoratedStory, innerStory) as VueRenderer['storyResult']; - }, - (context) => { - updatedArgs = {}; - const story = storyFn(context); - story.inheritAttrs ??= context.parameters.inheritAttrs ?? true; - return prepare(story) as LegacyStoryFn; - } - ); + return (context: StoryContext) => { + const story = decoratedStoryFn(context); + story.inheritAttrs ??= context.parameters.inheritAttrs ?? false; + return prepare(story) as LegacyStoryFn; + }; } diff --git a/code/renderers/vue3/src/render.ts b/code/renderers/vue3/src/render.ts index b3e6678f241b..120911cc0351 100644 --- a/code/renderers/vue3/src/render.ts +++ b/code/renderers/vue3/src/render.ts @@ -1,11 +1,10 @@ /* eslint-disable no-param-reassign */ import type { App, ConcreteComponent } from 'vue'; -import { createApp, h, reactive, isVNode, isReactive } from 'vue'; +import { createApp, h, reactive, isReactive } from 'vue'; import type { RenderContext, ArgsStoryFn } from '@storybook/types'; import type { Args, StoryContext } from '@storybook/csf'; -import { cloneDeep } from 'lodash'; -import type { VueRenderer, StoryFnVueReturnType, StoryID } from './types'; +import type { VueRenderer, StoryID } from './types'; const slotsMap = new Map< StoryID, @@ -60,10 +59,9 @@ export function renderToCanvas( // normally storyFn should be call once only in setup function,but because the nature of react and how storybook rendering the decorators // we need to call here to run the decorators again // i may wrap each decorator in memoized function to avoid calling it if the args are not changed - const element = storyFn(); // call the story function to get the root element with all the decorators - const args = getArgs(element, storyContext); // get args in case they are altered by decorators otherwise use the args from the context + storyFn(); // call the story function to run the decorators when the args are changed - updateArgs(existingApp.reactiveArgs, args); + updateArgs(existingApp.reactiveArgs, storyContext.args); return () => { teardown(existingApp.vueApp, canvasElement); }; @@ -75,7 +73,7 @@ export function renderToCanvas( setup() { storyContext.args = reactive(storyContext.args); const rootElement = storyFn(); // call the story function to get the root element with all the decorators - const args = getArgs(rootElement, storyContext); // get args in case they are altered by decorators otherwise use the args from the context + const { args } = storyContext; // get the args from the story context const appState = { vueApp, reactiveArgs: reactive(args), @@ -114,15 +112,6 @@ function generateSlots(context: StoryContext) { return reactive(Object.fromEntries(slots)); } -/** - * get the args from the root element props if it is a vnode otherwise from the context - * @param element is the root element of the story - * @param storyContext is the story context - */ - -function getArgs(element: StoryFnVueReturnType, storyContext: StoryContext) { - return element.props && isVNode(element) ? element.props : storyContext.args; -} /** * update the reactive args @@ -140,7 +129,7 @@ export function updateArgs(reactiveArgs: Args, nextArgs: Args) { } }); // update currentArgs with nextArgs - Object.assign(currentArgs, cloneDeep(nextArgs)); + Object.assign(currentArgs, nextArgs); } /** From 6b349ebdae42907ebbf84045306d06b9afed8598 Mon Sep 17 00:00:00 2001 From: chakAs3 Date: Sat, 10 Jun 2023 05:35:22 +0400 Subject: [PATCH 10/10] fix type checking --- code/renderers/vue3/src/decorateStory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/renderers/vue3/src/decorateStory.ts b/code/renderers/vue3/src/decorateStory.ts index a4545236bf13..9df22fa337e6 100644 --- a/code/renderers/vue3/src/decorateStory.ts +++ b/code/renderers/vue3/src/decorateStory.ts @@ -1,4 +1,4 @@ -import type { ConcreteComponent, Component, ComponentOptions } from 'vue'; +import type { ConcreteComponent, Component } from 'vue'; import { h } from 'vue'; import type { DecoratorFunction, StoryContext, LegacyStoryFn } from '@storybook/types'; import { sanitizeStoryContextUpdate } from '@storybook/preview-api';