diff --git a/code/renderers/vue3/src/render.ts b/code/renderers/vue3/src/render.ts index 9cb7973910ac..0c3102957a9c 100644 --- a/code/renderers/vue3/src/render.ts +++ b/code/renderers/vue3/src/render.ts @@ -1,6 +1,6 @@ /* eslint-disable no-param-reassign */ -import type { ConcreteComponent } from 'vue'; -import { createApp, h, isReactive, isVNode, reactive } from 'vue'; +import type { App, ConcreteComponent } from 'vue'; +import { createApp, h, reactive, isVNode, isReactive } from 'vue'; import type { RenderContext, ArgsStoryFn } from '@storybook/types'; import type { Args, StoryContext } from '@storybook/csf'; @@ -25,9 +25,18 @@ export const render: ArgsStoryFn = (props, context) => { return h(Component, props, createOrUpdateSlots(context)); }; -let setupFunction = (_app: any) => {}; -export const setup = (fn: (app: any) => void) => { - setupFunction = fn; +// set of setup functions that will be called when story is created +const setupFunctions = new Set<(app: App, storyContext?: StoryContext) => void>(); +/** add a setup function to set that will be call when story is created a d + * + * @param fn + */ +export const setup = (fn: (app: App, storyContext?: StoryContext) => void) => { + setupFunctions.add(fn); +}; + +const runSetupFunctions = (app: App, storyContext: StoryContext) => { + setupFunctions.forEach((fn) => fn(app, storyContext)); }; const map = new Map< @@ -77,8 +86,9 @@ export function renderToCanvas( }; }, }); + vueApp.config.errorHandler = (e: unknown) => showException(e as Error); - setupFunction(vueApp); + runSetupFunctions(vueApp, storyContext); vueApp.mount(canvasElement); showMain(); diff --git a/code/renderers/vue3/template/stories/GlobalSetup.stories.ts b/code/renderers/vue3/template/stories/GlobalSetup.stories.ts new file mode 100644 index 000000000000..585d8e35eda3 --- /dev/null +++ b/code/renderers/vue3/template/stories/GlobalSetup.stories.ts @@ -0,0 +1,52 @@ +import { expect } from '@storybook/jest'; +import type { Meta, StoryObj } from 'renderers/vue3/src'; +import { within } from '@storybook/testing-library'; +import { inject } from 'vue'; +import GlobalSetup from './GlobalSetup.vue'; + +const meta: Meta = { + component: GlobalSetup, + argTypes: {}, + render: (args: any) => ({ + // Components used in your story `template` are defined in the `components` object + components: { GlobalUsage: GlobalSetup }, + // The story's `args` need to be mapped into the template through the `setup()` method + setup() { + const color = inject('someColor', 'red'); // <-- this is the global setup from .storybook/preview.ts + return { args: { ...args, backgroundColor: color } }; + }, + // And then the `args` are bound to your component with `v-bind="args"` + template: '', + }), +} satisfies Meta; +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + args: { + primary: true, + label: 'someColor injected from .storybook/preview.ts', + }, + play: async ({ canvasElement, id }) => { + const canvas = within(canvasElement); + + const button = await canvas.getByRole('button'); + await expect(button).toHaveStyle('background-color: rgb(0, 128, 0)'); // <-- this provide themeColor = green from .storybook/preview.ts + + const h2 = await canvas.getByRole('heading', { level: 2 }); + await expect(h2).toHaveTextContent('Hi Story! from some plugin your name is Primary!'); + + const h3 = await canvas.getByRole('heading', { level: 3 }); + await expect(h3).toHaveTextContent('Hello Story! from some plugin your name is Primary!'); + + const h4 = await canvas.getByRole('heading', { level: 4 }); + await expect(h4).toHaveTextContent('Welcome Story! from some plugin your name is Primary!'); + }, +}; + +export const Secondary: Story = { + args: { + label: 'someColor injected from .storybook/preview.ts', + }, +}; diff --git a/code/renderers/vue3/template/stories/GlobalSetup.vue b/code/renderers/vue3/template/stories/GlobalSetup.vue new file mode 100644 index 000000000000..a87d13e74447 --- /dev/null +++ b/code/renderers/vue3/template/stories/GlobalSetup.vue @@ -0,0 +1,6 @@ + diff --git a/code/renderers/vue3/template/stories/preview.js b/code/renderers/vue3/template/stories/preview.js deleted file mode 100644 index c57ed02a172a..000000000000 --- a/code/renderers/vue3/template/stories/preview.js +++ /dev/null @@ -1,13 +0,0 @@ -import { global as globalThis } from '@storybook/global'; -// eslint-disable-next-line import/no-extraneous-dependencies -import { setup } from '@storybook/vue3'; - -// TODO: I'd like to be able to export rather than imperatively calling an imported function -// export const setup = (app) => { -// app.component('GlobalButton', Button); -// }; - -setup((app) => { - // This adds a component that can be used globally in stories - app.component('GlobalButton', globalThis.Components.Button); -}); diff --git a/code/renderers/vue3/template/stories/preview.ts b/code/renderers/vue3/template/stories/preview.ts new file mode 100644 index 000000000000..24d69086f648 --- /dev/null +++ b/code/renderers/vue3/template/stories/preview.ts @@ -0,0 +1,51 @@ +import type { App, Plugin } from 'vue'; +import type { StoryContext } from '@storybook/csf'; +import { global as globalThis } from '@storybook/global'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { setup, type VueRenderer } from '@storybook/vue3'; + +declare module 'vue' { + interface ComponentCustomProperties { + $greetingMessage: (key: string) => string; + } +} + +declare global { + // eslint-disable-next-line no-var,vars-on-top + var Components: Record; +} + +const somePlugin: Plugin = { + install: (app: App, options) => { + // inject a globally available $greetingText() method + // eslint-disable-next-line no-param-reassign + app.config.globalProperties.$greetingMessage = (key: string) => { + // retrieve a nested property in `options` + // using `key` + return options.greetings[key]; + }; + }, +}; +const someColor = 'someColor'; + +// add components to global scope +setup((app: App) => { + // This adds a component that can be used globally in stories + app.component('GlobalButton', globalThis.Components.Button); +}); + +// this adds a plugin to vue app that can be used globally in stories +setup((app: App, context?: StoryContext) => { + app.use(somePlugin, { + greetings: { + hello: `Hello Story! from some plugin your name is ${context?.name}!`, + welcome: `Welcome Story! from some plugin your name is ${context?.name}!`, + hi: `Hi Story! from some plugin your name is ${context?.name}!`, + }, + }); +}); + +// additonal setup to provide some propriety to the app +setup((app: App, context) => { + app.provide(someColor, 'green'); +}); diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/GlobalUsage.vue b/code/renderers/vue3/template/stories_vue3-vite-default-ts/GlobalUsage.vue index 3c4d63e3bc65..865c25306626 100644 --- a/code/renderers/vue3/template/stories_vue3-vite-default-ts/GlobalUsage.vue +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/GlobalUsage.vue @@ -1,3 +1,3 @@