From 07f07ec33f1ff3cbebb5d2354f553e22f1780bb4 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 2 Feb 2026 16:34:00 +0100 Subject: [PATCH] Revert "Vue: Make globals reactive in decorators" --- code/e2e-tests/framework-vue3.spec.ts | 49 ------------- code/renderers/vue3/src/render.test.ts | 29 ++------ code/renderers/vue3/src/render.ts | 23 ++----- .../decorators.stories.ts | 68 ------------------- .../decorator-with-reactive-globals.md | 52 -------------- docs/_snippets/decorator-with-updateArgs.md | 58 ---------------- docs/writing-stories/decorators.mdx | 24 ------- 7 files changed, 10 insertions(+), 293 deletions(-) delete mode 100644 code/e2e-tests/framework-vue3.spec.ts delete mode 100644 docs/_snippets/decorator-with-reactive-globals.md delete mode 100644 docs/_snippets/decorator-with-updateArgs.md diff --git a/code/e2e-tests/framework-vue3.spec.ts b/code/e2e-tests/framework-vue3.spec.ts deleted file mode 100644 index fbb76c026ccc..000000000000 --- a/code/e2e-tests/framework-vue3.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { expect, test } from '@playwright/test'; -import process from 'process'; - -import { SbPage } from './util'; - -const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:6006'; -const templateName = process.env.STORYBOOK_TEMPLATE_NAME; - -test.describe('Vue 3', () => { - test.beforeEach(async ({ page }) => { - await page.goto(storybookUrl); - await new SbPage(page, expect).waitUntilLoaded(); - }); - - test.skip(!templateName?.includes('vue3'), 'Only run these tests on Vue 3'); - - test('updateArgs works in decorators', async ({ page }) => { - const sbPage = new SbPage(page, expect); - - await sbPage.navigateToStory( - 'stories/renderers/vue3_vue3-vite-default-ts/decorators', - 'update-args' - ); - const previewRoot = sbPage.previewRoot(); - const button = previewRoot.getByRole('button', { name: 'Add 1' }); - - await expect(previewRoot).toContainText('0'); - await button.click(); - await expect(previewRoot).toContainText('1'); - await button.click(); - await expect(previewRoot).toContainText('2'); - }); - - test('Decorators can consume reactive globals', async ({ page }) => { - const sbPage = new SbPage(page, expect); - - await sbPage.navigateToStory( - 'stories/renderers/vue3_vue3-vite-default-ts/decorators', - 'reactive-global-decorator' - ); - - // Check the original language - await expect(sbPage.previewRoot()).toContainText('Hello'); - - // Select spanish in the locale toolbar and check that the text changes - await sbPage.selectToolbar('[aria-label^="Internationalization locale"]', 'text=/Español/'); - await expect(sbPage.previewRoot()).toContainText('Hola'); - }); -}); diff --git a/code/renderers/vue3/src/render.test.ts b/code/renderers/vue3/src/render.test.ts index f4fb1c38a082..98e48b86fd57 100644 --- a/code/renderers/vue3/src/render.test.ts +++ b/code/renderers/vue3/src/render.test.ts @@ -1,9 +1,7 @@ import { describe, expect, it } from 'vitest'; -import type { Args, Globals } from 'storybook/internal/types'; - import { expectTypeOf } from 'expect-type'; -import { computed, reactive } from 'vue'; +import { reactive } from 'vue'; import { updateArgs } from './render'; @@ -25,7 +23,7 @@ describe('Render Story', () => { expectTypeOf(reactiveArgs).toEqualTypeOf<{ objectArg: { argFoo: string; argBar: string } }>(); const newArgs = { argFoo: 'foo2', argBar: 'bar2' }; - updateArgs(reactiveArgs, newArgs); + updateArgs(reactiveArgs, newArgs); expectTypeOf(reactiveArgs).toEqualTypeOf<{ objectArg: { argFoo: string; argBar: string } }>(); expect(reactiveArgs).toEqual({ argFoo: 'foo2', @@ -39,7 +37,7 @@ describe('Render Story', () => { expectTypeOf(reactiveArgs).toEqualTypeOf<{ objectArg: { argFoo: string } }>(); const newArgs = { argFoo: 'foo2', argBar: 'bar2' }; - updateArgs(reactiveArgs, newArgs); + updateArgs(reactiveArgs, newArgs); expect(reactiveArgs).toEqual({ argFoo: 'foo2', argBar: 'bar2' }); }); @@ -55,7 +53,7 @@ describe('Render Story', () => { }>(); const newArgs = { argFoo: 'foo2', argBar: 'bar2' }; - updateArgs(reactiveArgs, newArgs); + updateArgs(reactiveArgs, newArgs); expect(reactiveArgs).toEqual({ argFoo: 'foo2', @@ -90,23 +88,4 @@ describe('Render Story', () => { expect(reactiveArgs).toEqual({ objectArg: { argFoo: 'bar' } }); }); - - it('update reactive Globals', async () => { - const reactiveGlobals = reactive({ theme: 'light', locale: 'en' }); - - let observedTheme: string | undefined; - const watcher = computed(() => { - observedTheme = reactiveGlobals.theme as string; - return reactiveGlobals.theme; - }); - - expect(watcher.value).toBe('light'); - expect(observedTheme).toBe('light'); - - updateArgs(reactiveGlobals, { theme: 'dark', locale: 'en' }); - - expect(watcher.value).toBe('dark'); - expect(observedTheme).toBe('dark'); - expect(reactiveGlobals).toEqual({ theme: 'dark', locale: 'en' }); - }); }); diff --git a/code/renderers/vue3/src/render.ts b/code/renderers/vue3/src/render.ts index b671e0a82861..1bc85770371e 100644 --- a/code/renderers/vue3/src/render.ts +++ b/code/renderers/vue3/src/render.ts @@ -1,11 +1,5 @@ /* eslint-disable local-rules/no-uncategorized-errors */ -import type { Globals } from 'storybook/internal/types'; -import { - type Args, - type ArgsStoryFn, - type RenderContext, - type StoryContext, -} from 'storybook/internal/types'; +import type { Args, ArgsStoryFn, RenderContext, StoryContext } from 'storybook/internal/types'; import type { PreviewWeb } from 'storybook/preview-api'; import type { App } from 'vue'; @@ -45,7 +39,6 @@ const map = new Map< { vueApp: ReturnType; reactiveArgs: Args; - reactiveGlobals: Globals; } >(); @@ -63,8 +56,7 @@ export async function renderToCanvas( 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 - updateArgs(existingApp.reactiveArgs, args); - updateArgs(existingApp.reactiveGlobals, storyContext.globals); + updateArgs(existingApp.reactiveArgs, args); return () => { teardown(existingApp.vueApp, canvasElement); }; @@ -74,19 +66,20 @@ export async function renderToCanvas( teardown(existingApp.vueApp, canvasElement); } + // create vue app for the story + // create vue app for the story const vueApp = createApp({ setup() { storyContext.args = reactive(storyContext.args); - storyContext.globals = reactive(storyContext.globals); 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 appState = { vueApp, reactiveArgs: reactive(args), - reactiveGlobals: storyContext.globals, }; map.set(canvasElement, appState); + return () => { // not passing args here as props // treat the rootElement as a component without props @@ -148,11 +141,7 @@ function getArgs(element: StoryFnVueReturnType, storyContext: StoryContext(reactiveArgs: T, nextArgs: T) { +export function updateArgs(reactiveArgs: Args, nextArgs: Args) { if (Object.keys(nextArgs).length === 0) { return; } diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/decorators.stories.ts b/code/renderers/vue3/template/stories_vue3-vite-default-ts/decorators.stories.ts index a5fc3bec526b..347ea2b6698e 100644 --- a/code/renderers/vue3/template/stories_vue3-vite-default-ts/decorators.stories.ts +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/decorators.stories.ts @@ -3,9 +3,7 @@ import type { DecoratorFunction } from 'storybook/internal/types'; import { global as globalThis } from '@storybook/global'; import type { Meta, StoryObj, VueRenderer } from '@storybook/vue3'; -import { useArgs } from 'storybook/preview-api'; import { h } from 'vue'; -import { computed } from 'vue'; const { Button, Pre } = (globalThis as any).__TEMPLATE_COMPONENTS__; @@ -49,62 +47,6 @@ const DynamicWrapperWrapper: DecoratorFunction = (storyFn, { args } computed: { level: () => `${args.level}px` }, }); -const getCaptionForLocale = (locale: string) => { - switch (locale) { - case 'es': - return 'Hola!'; - case 'kr': - return '안녕하세요!'; - case 'zh': - return '你好!'; - case 'en': - return 'Hello!'; - default: - return undefined; - } -}; - -const updateArgsDecorator: DecoratorFunction = (story, { args }) => { - const [, updateArgs] = useArgs(); - return { - components: { story }, - setup() { - return { - args, - updateArgs, - }; - }, - template: ` -
- -
- -
- `, - }; -}; - -const localeDecorator: DecoratorFunction = (story, { globals }) => { - return { - components: { story }, - setup() { - const ctxGreeting = computed(() => getCaptionForLocale(globals?.locale) || 'Hello!'); - - return { - ctxGreeting, - globals, - }; - }, - template: ` -
-

Greeting: {{ctxGreeting}}

-

Locale: {{globals?.locale}}

- -
- `, - }; -}; - export const ComponentTemplate: Story = { args: { label: 'With component' }, decorators: [ComponentTemplateWrapper], @@ -142,13 +84,3 @@ export const MultipleWrappers = { DynamicWrapperWrapper, ], }; - -export const UpdateArgs = { - args: { label: '0' }, - decorators: [updateArgsDecorator], -}; - -export const ReactiveGlobalDecorator = { - args: { label: 'With reactive global decorator' }, - decorators: [localeDecorator], -}; diff --git a/docs/_snippets/decorator-with-reactive-globals.md b/docs/_snippets/decorator-with-reactive-globals.md deleted file mode 100644 index 12190d20695c..000000000000 --- a/docs/_snippets/decorator-with-reactive-globals.md +++ /dev/null @@ -1,52 +0,0 @@ -```js filename=".storybook/preview.js" renderer="vue" language="js" -import { computed } from 'vue'; - -export default { - decorators: [ - (story, { globals }) => { - return { - components: { story }, - setup() { - const greeting = computed(() => globals?.locale === 'en' ? 'Hello!' : '¡Hola!'); - - return { greeting, globals }; - }, - template: ` -
-

Greeting: {{greeting}}

- -
- `, - }; - }, - ], -}; -``` - -```ts filename=".storybook/preview.ts" renderer="vue" language="ts" -import { computed } from 'vue'; -import type { Preview } from '@storybook/vue3-vite'; - -const preview: Preview = { - decorators: [ - (story, { globals }) => { - return { - components: { story }, - setup() { - const greeting = computed(() => globals?.locale === 'en' ? 'Hello!' : '¡Hola!'); - - return { greeting, globals }; - }, - template: ` -
-

Greeting: {{greeting}}

- -
- `, - }; - }, - ], -}; - -export default preview; -``` diff --git a/docs/_snippets/decorator-with-updateArgs.md b/docs/_snippets/decorator-with-updateArgs.md deleted file mode 100644 index 3212cae0fc1b..000000000000 --- a/docs/_snippets/decorator-with-updateArgs.md +++ /dev/null @@ -1,58 +0,0 @@ -```js filename=".storybook/preview.js" renderer="vue" language="js" -import { useArgs } from 'storybook/preview-api'; - -const WithIncrementDecorator = { - args: { - counter: 0, - }, - decorators: [ - (story, { args }) => { - const [, updateArgs] = useArgs(); - return { - components: { story }, - setup() { - return { args, updateArgs }; - }, - template: ` -
- - -
- `, - }; - }, - ], -}; -``` - -```ts filename=".storybook/preview.ts" renderer="vue" language="ts" -import { useArgs } from 'storybook/preview-api'; -import type { Meta, StoryObj } from '@storybook/vue3'; - -const WithIncrementDecorator: StoryObj> = { - args: { - counter: 0, - }, - decorators: [ - (story, { args }) => { - const [, updateArgs] = useArgs(); - return { - components: { story }, - setup() { - return { args, updateArgs }; - }, - template: ` -
- - -
- `, - }; - }, - ], -}; -``` diff --git a/docs/writing-stories/decorators.mdx b/docs/writing-stories/decorators.mdx index b60532bd9ac4..7acaf6bdfa33 100644 --- a/docs/writing-stories/decorators.mdx +++ b/docs/writing-stories/decorators.mdx @@ -79,30 +79,6 @@ This context can be used to adjust the behavior of your decorator based on the s For another example, see the section on [configuring the mock provider](./mocking-data-and-modules/mocking-providers.mdx#configuring-the-mock-provider), which demonstrates how to use the same technique to change which theme is provided to the component. - - -### Reactive globals - -To ensure `globals` in Vue decorators are fully reactive, you must pass them through the `setup` function. You can also compute derived values with Vue's `computed` function. - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -### Preview API hooks - -The same applies to Storybook hooks you want to call in your decorator. For instance, this decorator increments a counter used by its decorated story. - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - - - ### Using decorators to provide data If your components are “connected” and require side-loaded data to render, you can use decorators to provide that data in a mocked way without having to refactor your components to take that data as an arg. There are several techniques to achieve this. Depending on exactly how you are loading that data. Read more in the [building pages in Storybook](./build-pages-with-storybook.mdx) section.