diff --git a/AGENTS.md b/AGENTS.md index a62d7ddb5bf1..f462a3cb9d09 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,6 +8,7 @@ This file is the canonical instruction source for coding agents. Files like `CLA Storybook is a large TypeScript monorepo. The git root is the repo root, the main code lives in `code/`, and build tooling lives in `scripts/`. +- **Base branch**: `next` (all PRs should target `next`, not `main`) - **Node.js**: `22.21.1` (see `.nvmrc`) - **Package Manager**: Yarn Berry - **Task orchestration**: NX plus the custom `yarn task` runner diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 5e5a3d3692da..9f2bf2961789 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,8 @@ +## 10.3.0-beta.1 + +- Addon-Docs: Add React as optimizeDeps entry - [#34176](https://github.com/storybookjs/storybook/pull/34176), thanks @valentinpalkovic! +- CLI: Avoid hanging of postinstall during init - [#34175](https://github.com/storybookjs/storybook/pull/34175), thanks @valentinpalkovic! + ## 10.3.0-beta.0 - Test: Fix clearing mocks in Vitest [#34078](https://github.com/storybookjs/storybook/pull/34078) diff --git a/code/addons/a11y/package.json b/code/addons/a11y/package.json index b59e23ba21ab..2902a1040c72 100644 --- a/code/addons/a11y/package.json +++ b/code/addons/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-a11y", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook Addon A11y: Test UI component compliance with WCAG web accessibility standards", "keywords": [ "a11y", diff --git a/code/addons/docs/package.json b/code/addons/docs/package.json index be7ad80528e1..b681f538b9e3 100644 --- a/code/addons/docs/package.json +++ b/code/addons/docs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-docs", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook Docs: Document UI components automatically with stories and MDX", "keywords": [ "docs", diff --git a/code/addons/docs/src/preset.ts b/code/addons/docs/src/preset.ts index 2aff529e0bcb..f65bf06b228a 100644 --- a/code/addons/docs/src/preset.ts +++ b/code/addons/docs/src/preset.ts @@ -218,6 +218,7 @@ const optimizeViteDeps = [ '@storybook/addon-docs > @storybook/react-dom-shim', 'react-dom/client', 'react/jsx-runtime', + 'react', ]; export { webpackX as webpack, docsX as docs, optimizeViteDeps }; diff --git a/code/addons/links/package.json b/code/addons/links/package.json index 5439bbd807e5..26dc0f479d73 100644 --- a/code/addons/links/package.json +++ b/code/addons/links/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-links", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook Links: Link stories together to build demos and prototypes with your UI components", "keywords": [ "storybook", diff --git a/code/addons/onboarding/package.json b/code/addons/onboarding/package.json index 8857f6a8000f..a13c19b15e33 100644 --- a/code/addons/onboarding/package.json +++ b/code/addons/onboarding/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-onboarding", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook Onboarding: Help new users learn how to write stories", "keywords": [ "storybook", diff --git a/code/addons/onboarding/src/features/IntentSurvey/IntentSurvey.stories.tsx b/code/addons/onboarding/src/features/IntentSurvey/IntentSurvey.stories.tsx index 2ec2107481e9..12c7a0b30a4e 100644 --- a/code/addons/onboarding/src/features/IntentSurvey/IntentSurvey.stories.tsx +++ b/code/addons/onboarding/src/features/IntentSurvey/IntentSurvey.stories.tsx @@ -21,23 +21,23 @@ export const Default: Story = {}; export const Submitting: Story = { play: async ({ args }) => { const button = await screen.findByRole('button', { name: 'Submit' }); - await expect(button).toBeDisabled(); + await expect(button).toHaveAttribute('aria-disabled', 'true'); await userEvent.click(await screen.findByText('Design system')); - await expect(button).toBeDisabled(); + await expect(button).toHaveAttribute('aria-disabled', 'true'); await userEvent.click(await screen.findByText('Functional testing')); await userEvent.click(await screen.findByText('Accessibility testing')); await userEvent.click(await screen.findByText('Visual testing')); - await expect(button).toBeDisabled(); + await expect(button).toHaveAttribute('aria-disabled', 'true'); await userEvent.selectOptions(screen.getByRole('combobox'), ['We use it at work']); - await expect(button).not.toBeDisabled(); + await expect(button).not.toHaveAttribute('aria-disabled', 'true'); await userEvent.click(button); await waitFor(async () => { - await expect(button).toBeDisabled(); + await expect(button).toHaveAttribute('aria-disabled', 'true'); await expect(args.onComplete).toHaveBeenCalledWith({ building: { 'application-ui': false, diff --git a/code/addons/pseudo-states/package.json b/code/addons/pseudo-states/package.json index c03a7e4c6077..f8686701f659 100644 --- a/code/addons/pseudo-states/package.json +++ b/code/addons/pseudo-states/package.json @@ -1,6 +1,6 @@ { "name": "storybook-addon-pseudo-states", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook Pseudo-states addon: Manipulate CSS pseudo states", "keywords": [ "storybook", diff --git a/code/addons/themes/package.json b/code/addons/themes/package.json index 6e1f28e3412c..55aa7bead9bf 100644 --- a/code/addons/themes/package.json +++ b/code/addons/themes/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-themes", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook Themes addon: Switch between themes from the toolbar", "keywords": [ "css", diff --git a/code/addons/vitest/package.json b/code/addons/vitest/package.json index 541e8719a446..8da5d09d5ac7 100644 --- a/code/addons/vitest/package.json +++ b/code/addons/vitest/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-vitest", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook Vitest addon: Blazing fast component testing using stories", "keywords": [ "storybook", diff --git a/code/builders/builder-vite/package.json b/code/builders/builder-vite/package.json index 1f4f2869ed2a..46e7121bc2b4 100644 --- a/code/builders/builder-vite/package.json +++ b/code/builders/builder-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-vite", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "A Storybook builder to dev and build with Vite", "keywords": [ "storybook", diff --git a/code/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json index c9f494c25b02..9dd1e2a94830 100644 --- a/code/builders/builder-webpack5/package.json +++ b/code/builders/builder-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-webpack5", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "A Storybook builder to dev and build with Webpack", "keywords": [ "storybook", diff --git a/code/core/package.json b/code/core/package.json index dc410d3f6cc2..2956c06590ca 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -1,6 +1,6 @@ { "name": "storybook", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/core/src/common/versions.ts b/code/core/src/common/versions.ts index d0e1c4a5f0d2..9b89d803329b 100644 --- a/code/core/src/common/versions.ts +++ b/code/core/src/common/versions.ts @@ -1,45 +1,45 @@ // auto generated file, do not edit export default { - '@storybook/addon-a11y': '10.3.0-beta.0', - '@storybook/addon-docs': '10.3.0-beta.0', - '@storybook/addon-links': '10.3.0-beta.0', - '@storybook/addon-onboarding': '10.3.0-beta.0', - 'storybook-addon-pseudo-states': '10.3.0-beta.0', - '@storybook/addon-themes': '10.3.0-beta.0', - '@storybook/addon-vitest': '10.3.0-beta.0', - '@storybook/builder-vite': '10.3.0-beta.0', - '@storybook/builder-webpack5': '10.3.0-beta.0', - storybook: '10.3.0-beta.0', - '@storybook/angular': '10.3.0-beta.0', - '@storybook/ember': '10.3.0-beta.0', - '@storybook/html-vite': '10.3.0-beta.0', - '@storybook/nextjs': '10.3.0-beta.0', - '@storybook/nextjs-vite': '10.3.0-beta.0', - '@storybook/preact-vite': '10.3.0-beta.0', - '@storybook/react-native-web-vite': '10.3.0-beta.0', - '@storybook/react-vite': '10.3.0-beta.0', - '@storybook/react-webpack5': '10.3.0-beta.0', - '@storybook/server-webpack5': '10.3.0-beta.0', - '@storybook/svelte-vite': '10.3.0-beta.0', - '@storybook/sveltekit': '10.3.0-beta.0', - '@storybook/vue3-vite': '10.3.0-beta.0', - '@storybook/web-components-vite': '10.3.0-beta.0', - sb: '10.3.0-beta.0', - '@storybook/cli': '10.3.0-beta.0', - '@storybook/codemod': '10.3.0-beta.0', - '@storybook/core-webpack': '10.3.0-beta.0', - 'create-storybook': '10.3.0-beta.0', - '@storybook/csf-plugin': '10.3.0-beta.0', - 'eslint-plugin-storybook': '10.3.0-beta.0', - '@storybook/react-dom-shim': '10.3.0-beta.0', - '@storybook/preset-create-react-app': '10.3.0-beta.0', - '@storybook/preset-react-webpack': '10.3.0-beta.0', - '@storybook/preset-server-webpack': '10.3.0-beta.0', - '@storybook/html': '10.3.0-beta.0', - '@storybook/preact': '10.3.0-beta.0', - '@storybook/react': '10.3.0-beta.0', - '@storybook/server': '10.3.0-beta.0', - '@storybook/svelte': '10.3.0-beta.0', - '@storybook/vue3': '10.3.0-beta.0', - '@storybook/web-components': '10.3.0-beta.0', + '@storybook/addon-a11y': '10.3.0-beta.1', + '@storybook/addon-docs': '10.3.0-beta.1', + '@storybook/addon-links': '10.3.0-beta.1', + '@storybook/addon-onboarding': '10.3.0-beta.1', + 'storybook-addon-pseudo-states': '10.3.0-beta.1', + '@storybook/addon-themes': '10.3.0-beta.1', + '@storybook/addon-vitest': '10.3.0-beta.1', + '@storybook/builder-vite': '10.3.0-beta.1', + '@storybook/builder-webpack5': '10.3.0-beta.1', + storybook: '10.3.0-beta.1', + '@storybook/angular': '10.3.0-beta.1', + '@storybook/ember': '10.3.0-beta.1', + '@storybook/html-vite': '10.3.0-beta.1', + '@storybook/nextjs': '10.3.0-beta.1', + '@storybook/nextjs-vite': '10.3.0-beta.1', + '@storybook/preact-vite': '10.3.0-beta.1', + '@storybook/react-native-web-vite': '10.3.0-beta.1', + '@storybook/react-vite': '10.3.0-beta.1', + '@storybook/react-webpack5': '10.3.0-beta.1', + '@storybook/server-webpack5': '10.3.0-beta.1', + '@storybook/svelte-vite': '10.3.0-beta.1', + '@storybook/sveltekit': '10.3.0-beta.1', + '@storybook/vue3-vite': '10.3.0-beta.1', + '@storybook/web-components-vite': '10.3.0-beta.1', + sb: '10.3.0-beta.1', + '@storybook/cli': '10.3.0-beta.1', + '@storybook/codemod': '10.3.0-beta.1', + '@storybook/core-webpack': '10.3.0-beta.1', + 'create-storybook': '10.3.0-beta.1', + '@storybook/csf-plugin': '10.3.0-beta.1', + 'eslint-plugin-storybook': '10.3.0-beta.1', + '@storybook/react-dom-shim': '10.3.0-beta.1', + '@storybook/preset-create-react-app': '10.3.0-beta.1', + '@storybook/preset-react-webpack': '10.3.0-beta.1', + '@storybook/preset-server-webpack': '10.3.0-beta.1', + '@storybook/html': '10.3.0-beta.1', + '@storybook/preact': '10.3.0-beta.1', + '@storybook/react': '10.3.0-beta.1', + '@storybook/server': '10.3.0-beta.1', + '@storybook/svelte': '10.3.0-beta.1', + '@storybook/vue3': '10.3.0-beta.1', + '@storybook/web-components': '10.3.0-beta.1', }; diff --git a/code/core/src/component-testing/components/test-fn.stories.tsx b/code/core/src/component-testing/components/test-fn.stories.tsx index 6b926a961913..d64622a2a07f 100644 --- a/code/core/src/component-testing/components/test-fn.stories.tsx +++ b/code/core/src/component-testing/components/test-fn.stories.tsx @@ -96,7 +96,7 @@ TestFunctionTypes.test( }, async ({ canvas }) => { const button = canvas.getByText('Arg from story'); - await expect(button).toBeEnabled(); + await expect(button).not.toHaveAttribute('aria-disabled', 'true'); } ); @@ -106,7 +106,7 @@ export const ExtendedStorySinglePlayExample = TestFunctionTypes.extend({ }, play: async ({ canvas }) => { const button = canvas.getByText('Arg from extended story'); - await expect(button).toBeEnabled(); + await expect(button).not.toHaveAttribute('aria-disabled', 'true'); }, }); @@ -120,7 +120,7 @@ ExtendedStorySingleTestExample.test( 'this is a very long test name to explain that this story test should guarantee that the args have been extended correctly', async ({ canvas }) => { const button = canvas.getByText('Arg from extended story'); - await expect(button).toBeEnabled(); + await expect(button).not.toHaveAttribute('aria-disabled', 'true'); } ); diff --git a/code/core/src/components/components/Button/Button.stories.tsx b/code/core/src/components/components/Button/Button.stories.tsx index acf3214bb0e9..26f654a442ca 100644 --- a/code/core/src/components/components/Button/Button.stories.tsx +++ b/code/core/src/components/components/Button/Button.stories.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { FaceHappyIcon } from '@storybook/icons'; -import { fn } from 'storybook/test'; +import { expect, fn } from 'storybook/test'; import { styled } from 'storybook/theming'; import preview from '../../../../../.storybook/preview'; @@ -291,6 +291,32 @@ export const Disabled = meta.story({ ariaLabel: false, disabled: true, children: 'Disabled Button', + onClick: fn(), + }, + render: (args) => ( + + + + ), + play: async ({ args, canvas, step }) => { + const button = canvas.getByRole('button', { name: 'Disabled Button' }); + + await step('Disabled button should be aria-disabled', async () => { + expect(button).toHaveAttribute('aria-disabled', 'true'); + }); + + await step('Disabled button should not be clickable', async () => { + button.click(); + expect(args.onClick).not.toHaveBeenCalled(); + }); + + await step('Disabled button should be focusable for accessibility', async () => { + const button = canvas.getByRole('button', { name: 'Disabled Button' }); + button.focus(); + expect(button).toHaveFocus(); + }); }, }); diff --git a/code/core/src/components/components/Button/Button.tsx b/code/core/src/components/components/Button/Button.tsx index dbf24c1622df..f7c8104b03ba 100644 --- a/code/core/src/components/components/Button/Button.tsx +++ b/code/core/src/components/components/Button/Button.tsx @@ -138,12 +138,13 @@ export const Button = forwardRef( variant={variant} size={size} padding={padding} - disabled={disabled || readOnly} + $disabled={disabled || readOnly} + aria-disabled={disabled || readOnly ? 'true' : undefined} readOnly={readOnly} active={active} animating={isAnimating} animation={animation} - onClick={handleClick} + onClick={disabled || readOnly ? undefined : handleClick} aria-label={!readOnly && ariaLabel !== false ? ariaLabel : undefined} aria-keyshortcuts={readOnly ? undefined : shortcutAttribute} {...(readOnly ? {} : ariaDescriptionAttrs)} @@ -165,7 +166,7 @@ const StyledButton = styled('button', { padding?: 'small' | 'medium' | 'none'; variant?: 'outline' | 'solid' | 'ghost'; active?: boolean; - disabled?: boolean; + $disabled?: boolean; readOnly?: boolean; animating?: boolean; animation?: 'none' | 'rotate360' | 'glow' | 'jiggle'; @@ -174,7 +175,7 @@ const StyledButton = styled('button', { theme, variant, size, - disabled, + $disabled, readOnly, active, animating, @@ -182,7 +183,7 @@ const StyledButton = styled('button', { padding, }) => ({ border: 0, - cursor: readOnly ? 'inherit' : disabled ? 'not-allowed' : 'pointer', + cursor: readOnly ? 'inherit' : $disabled ? 'not-allowed' : 'pointer', display: 'inline-flex', gap: '6px', alignItems: 'center', @@ -216,7 +217,7 @@ const StyledButton = styled('button', { verticalAlign: 'top', whiteSpace: 'nowrap', userSelect: 'none', - opacity: disabled && !readOnly ? 0.5 : 1, + opacity: $disabled && !readOnly ? 0.5 : 1, margin: 0, fontSize: `${theme.typography.size.s1}px`, fontWeight: theme.typography.weight.bold, diff --git a/code/core/src/manager-api/modules/shortcuts.ts b/code/core/src/manager-api/modules/shortcuts.ts index 7895ec219f8a..58f352467407 100644 --- a/code/core/src/manager-api/modules/shortcuts.ts +++ b/code/core/src/manager-api/modules/shortcuts.ts @@ -16,6 +16,10 @@ import { focusableUIElements } from './layout'; const { navigator, document } = global; +function wasFocusInElement(element: HTMLElement | null) { + return document.activeElement && element?.contains(document.activeElement); +} + export const isMacLike = () => navigator && navigator.platform ? !!navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) : false; export const controlOrMetaKey = () => (isMacLike() ? 'meta' : 'control'); @@ -359,13 +363,11 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) => { case 'togglePanel': { const wasPanelShown = fullAPI.getIsPanelShown(); - const panelElement = document.getElementById(focusableUIElements.storyPanelRoot); - const wasFocusInPanel = - panelElement && document.activeElement && panelElement.contains(document.activeElement); + const panelElement = document.getElementById(focusableUIElements.addonPanel); fullAPI.togglePanel(); - if (wasPanelShown && wasFocusInPanel) { + if (wasPanelShown && wasFocusInElement(panelElement)) { // poll: true always returns a Promise. ( fullAPI.focusOnUIElement(focusableUIElements.showAddonPanel, { @@ -384,11 +386,10 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) => { case 'toggleNav': { const wasNavShown = fullAPI.getIsNavShown(); const sidebarElement = document.getElementById(focusableUIElements.sidebarRegion); - const wasFocusInSidebar = sidebarElement?.contains(document?.activeElement); fullAPI.toggleNav(); - if (wasNavShown && wasFocusInSidebar) { + if (wasNavShown && wasFocusInElement(sidebarElement)) { // poll: true always returns a Promise. ( fullAPI.focusOnUIElement(focusableUIElements.showSidebar, { diff --git a/code/core/src/manager-api/version.ts b/code/core/src/manager-api/version.ts index e60d520d9aaf..a98b928e9511 100644 --- a/code/core/src/manager-api/version.ts +++ b/code/core/src/manager-api/version.ts @@ -1 +1 @@ -export const version = '10.3.0-beta.0'; +export const version = '10.3.0-beta.1'; diff --git a/code/core/src/manager/components/layout/Layout.tsx b/code/core/src/manager/components/layout/Layout.tsx index 2b1abc7d5e76..b10f0c36b4bd 100644 --- a/code/core/src/manager/components/layout/Layout.tsx +++ b/code/core/src/manager/components/layout/Layout.tsx @@ -201,7 +201,7 @@ export const Layout = ({ managerLayoutState, setManagerLayoutState, hasTab, ...s slotPages={slots.slotPages} /> - {isDesktop && ( + {isDesktop && showPanel && ( - {showPanel && slots.slotPanel} + {slots.slotPanel} )} {isMobile && } diff --git a/code/core/src/manager/components/sidebar/TagsFilter.stories.tsx b/code/core/src/manager/components/sidebar/TagsFilter.stories.tsx index 35d51c4faf00..aa3b551e47e8 100644 --- a/code/core/src/manager/components/sidebar/TagsFilter.stories.tsx +++ b/code/core/src/manager/components/sidebar/TagsFilter.stories.tsx @@ -207,9 +207,9 @@ export const ResetToDefaults: Story = { const resetButton = await screen.findByRole('button', { name: 'Reset filters' }); expect(resetButton).toBeInTheDocument(); - expect(resetButton).not.toBeDisabled(); + expect(resetButton).not.toHaveAttribute('aria-disabled', 'true'); resetButton.click(); - await waitFor(() => expect(resetButton).toBeDisabled()); + await waitFor(() => expect(resetButton).toHaveAttribute('aria-disabled', 'true')); }, } satisfies Story; diff --git a/code/core/src/manager/components/sidebar/TagsFilterPanel.tsx b/code/core/src/manager/components/sidebar/TagsFilterPanel.tsx index cf172dd78a40..4ca13a811c42 100644 --- a/code/core/src/manager/components/sidebar/TagsFilterPanel.tsx +++ b/code/core/src/manager/components/sidebar/TagsFilterPanel.tsx @@ -59,6 +59,7 @@ interface TagsFilterPanelProps { excludedFilters: string[]; } +/* Those tags are hidden in the UI. There's a more general built-in list defined in `shared/constants/tags`. */ const BUILT_IN_TAGS = new Set([ 'dev', 'test', @@ -67,6 +68,7 @@ const BUILT_IN_TAGS = new Set([ 'unattached-mdx', 'play-fn', 'test-fn', + 'manifest', ]); // This equality check works on the basis that there are no duplicates in the arrays. diff --git a/code/core/src/shared/checklist-store/checklistData.tsx b/code/core/src/shared/checklist-store/checklistData.tsx index c79ac0fe1eec..5dbf14ff8629 100644 --- a/code/core/src/shared/checklist-store/checklistData.tsx +++ b/code/core/src/shared/checklist-store/checklistData.tsx @@ -718,7 +718,7 @@ export const Disabled: Story = { await userEvent.click(button); // 👇 Make assertions - await expect(button).toBeDisabled(); + await expect(button).toHaveAttribute('aria-disabled', 'true'); await expect(args.onClick).not.toHaveBeenCalled(); } };`} diff --git a/code/e2e-tests/addon-toolbars.spec.ts b/code/e2e-tests/addon-toolbars.spec.ts index e47ee783a744..45a2cf3dbf4d 100644 --- a/code/e2e-tests/addon-toolbars.spec.ts +++ b/code/e2e-tests/addon-toolbars.spec.ts @@ -32,6 +32,6 @@ test.describe('addon-toolbars', () => { await expect(sbPage.previewRoot()).toContainText('안녕하세요'); const button = sbPage.page.getByLabel('Internationalization locale'); - await expect(button).toBeDisabled(); + await expect(button).toHaveAttribute('aria-disabled', 'true'); }); }); diff --git a/code/e2e-tests/addon-viewport.spec.ts b/code/e2e-tests/addon-viewport.spec.ts index ff8450c3905d..b93b4aecd3b5 100644 --- a/code/e2e-tests/addon-viewport.spec.ts +++ b/code/e2e-tests/addon-viewport.spec.ts @@ -54,6 +54,6 @@ test.describe('addon-viewport', () => { const toolbar = page.getByLabel('Viewport size'); - await expect(toolbar).toBeDisabled(); + await expect(toolbar).toHaveAttribute('aria-disabled', 'true'); }); }); diff --git a/code/e2e-tests/framework-vue3.spec.ts b/code/e2e-tests/framework-vue3.spec.ts new file mode 100644 index 000000000000..a06d6a02f2c4 --- /dev/null +++ b/code/e2e-tests/framework-vue3.spec.ts @@ -0,0 +1,49 @@ +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 !== 'vue3-vite/default-ts', '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/frameworks/angular/package.json b/code/frameworks/angular/package.json index 673ed3655867..b328d0404069 100644 --- a/code/frameworks/angular/package.json +++ b/code/frameworks/angular/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/angular", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for Angular: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/ember/package.json b/code/frameworks/ember/package.json index d9d9d768a9d5..3dccd218d5bb 100644 --- a/code/frameworks/ember/package.json +++ b/code/frameworks/ember/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/ember", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for Ember: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/html-vite/package.json b/code/frameworks/html-vite/package.json index 69ebfc717043..5d50fa1913cd 100644 --- a/code/frameworks/html-vite/package.json +++ b/code/frameworks/html-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html-vite", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for HTML and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/nextjs-vite/package.json b/code/frameworks/nextjs-vite/package.json index 8ae9233542e0..9dc52eaf04e6 100644 --- a/code/frameworks/nextjs-vite/package.json +++ b/code/frameworks/nextjs-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/nextjs-vite", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for Next.js and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index a4fe74cf31a7..71315ad41a36 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/nextjs", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for Next.js: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/preact-vite/package.json b/code/frameworks/preact-vite/package.json index aff382cc0a0f..0f9793e904d7 100644 --- a/code/frameworks/preact-vite/package.json +++ b/code/frameworks/preact-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact-vite", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for Preact and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/react-native-web-vite/package.json b/code/frameworks/react-native-web-vite/package.json index 4cda59d05444..e9b7818c2add 100644 --- a/code/frameworks/react-native-web-vite/package.json +++ b/code/frameworks/react-native-web-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-native-web-vite", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for React Native Web and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json index 7733ddfaa984..69fa10365445 100644 --- a/code/frameworks/react-vite/package.json +++ b/code/frameworks/react-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-vite", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for React and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/react-webpack5/package.json b/code/frameworks/react-webpack5/package.json index d62926d104db..155333dc1c34 100644 --- a/code/frameworks/react-webpack5/package.json +++ b/code/frameworks/react-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-webpack5", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for React and Webpack: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/server-webpack5/package.json b/code/frameworks/server-webpack5/package.json index 19c5c72e9f5b..004421483b70 100644 --- a/code/frameworks/server-webpack5/package.json +++ b/code/frameworks/server-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/server-webpack5", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", "keywords": [ "storybook", diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json index 0622e9d01e8a..1de92680eab3 100644 --- a/code/frameworks/svelte-vite/package.json +++ b/code/frameworks/svelte-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte-vite", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for Svelte and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/sveltekit/package.json b/code/frameworks/sveltekit/package.json index ab7c06def91a..2f76e258ce9c 100644 --- a/code/frameworks/sveltekit/package.json +++ b/code/frameworks/sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/sveltekit", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for SvelteKit: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/vue3-vite/package.json b/code/frameworks/vue3-vite/package.json index cf3b6e604581..53e51c1c993c 100644 --- a/code/frameworks/vue3-vite/package.json +++ b/code/frameworks/vue3-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue3-vite", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for Vue3 and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/web-components-vite/package.json b/code/frameworks/web-components-vite/package.json index 71d170154c10..c1da46f0ecf4 100644 --- a/code/frameworks/web-components-vite/package.json +++ b/code/frameworks/web-components-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components-vite", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for Web Components and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/lib/cli-sb/package.json b/code/lib/cli-sb/package.json index 299e9fb282b2..013c03eaa7bc 100644 --- a/code/lib/cli-sb/package.json +++ b/code/lib/cli-sb/package.json @@ -1,6 +1,6 @@ { "name": "sb", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook CLI: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/lib/cli-storybook/package.json b/code/lib/cli-storybook/package.json index 1649004724bb..ec1e4bacec89 100644 --- a/code/lib/cli-storybook/package.json +++ b/code/lib/cli-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/cli", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook CLI: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json index 023d18543bd3..70efb8ac4dbb 100644 --- a/code/lib/codemod/package.json +++ b/code/lib/codemod/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/codemod", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "A collection of codemod scripts written with JSCodeshift", "keywords": [ "storybook" diff --git a/code/lib/core-webpack/package.json b/code/lib/core-webpack/package.json index 73f76285a9e7..d3d901646f76 100644 --- a/code/lib/core-webpack/package.json +++ b/code/lib/core-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/core-webpack", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/lib/create-storybook/package.json b/code/lib/create-storybook/package.json index 2636153879a0..9c4438f74adf 100644 --- a/code/lib/create-storybook/package.json +++ b/code/lib/create-storybook/package.json @@ -1,6 +1,6 @@ { "name": "create-storybook", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook installer: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/lib/create-storybook/src/commands/AddonConfigurationCommand.test.ts b/code/lib/create-storybook/src/commands/AddonConfigurationCommand.test.ts index 2df6ed0ca21a..27293bc6c2ee 100644 --- a/code/lib/create-storybook/src/commands/AddonConfigurationCommand.test.ts +++ b/code/lib/create-storybook/src/commands/AddonConfigurationCommand.test.ts @@ -96,7 +96,7 @@ describe('AddonConfigurationCommand', () => { expect(addonVitestPostinstall).toHaveBeenCalledWith({ packageManager: 'npm', configDir: '.storybook', - yes: false, + yes: true, skipInstall: true, skipDependencyManagement: true, logger, @@ -122,7 +122,7 @@ describe('AddonConfigurationCommand', () => { expect(addonA11yPostinstall).toHaveBeenCalledWith({ packageManager: 'npm', configDir: '.storybook', - yes: false, + yes: true, skipInstall: true, skipDependencyManagement: true, logger, @@ -144,7 +144,7 @@ describe('AddonConfigurationCommand', () => { expect(postinstallAddon).toHaveBeenCalledWith('@storybook/addon-docs', { packageManager: 'npm', configDir: '.storybook', - yes: false, + yes: true, skipInstall: true, skipDependencyManagement: true, logger, @@ -324,7 +324,7 @@ describe('executeAddonConfiguration', () => { it('should create command and execute with provided parameters', async () => { const result = await executeAddonConfiguration({ packageManager: mockPackageManager, - options: { packageManager: PackageManagerName.NPM, yes: false, disableTelemetry: true }, + options: { packageManager: PackageManagerName.NPM, disableTelemetry: true }, addons: [], configDir: '.storybook', }); @@ -335,7 +335,7 @@ describe('executeAddonConfiguration', () => { it('should execute addon configuration through helper function', async () => { const result = await executeAddonConfiguration({ packageManager: mockPackageManager, - options: { packageManager: PackageManagerName.NPM, yes: true, disableTelemetry: false }, + options: { packageManager: PackageManagerName.NPM, disableTelemetry: false }, addons: ['@storybook/addon-a11y'], configDir: '.storybook', }); diff --git a/code/lib/create-storybook/src/commands/AddonConfigurationCommand.ts b/code/lib/create-storybook/src/commands/AddonConfigurationCommand.ts index 017fdb166150..fe9de34d6202 100644 --- a/code/lib/create-storybook/src/commands/AddonConfigurationCommand.ts +++ b/code/lib/create-storybook/src/commands/AddonConfigurationCommand.ts @@ -126,7 +126,7 @@ export class AddonConfigurationCommand { const options = { packageManager: this.packageManager.type, configDir, - yes: this.commandOptions.yes, + yes: true, skipInstall: true, skipDependencyManagement: true, logger, diff --git a/code/lib/csf-plugin/package.json b/code/lib/csf-plugin/package.json index e1bc6abf33e1..bede0792fce9 100644 --- a/code/lib/csf-plugin/package.json +++ b/code/lib/csf-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/csf-plugin", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Enrich CSF files via static analysis", "keywords": [ "storybook" diff --git a/code/lib/eslint-plugin/package.json b/code/lib/eslint-plugin/package.json index b7aadfb9e8a0..acec622cd26f 100644 --- a/code/lib/eslint-plugin/package.json +++ b/code/lib/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-storybook", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook ESLint Plugin: Best practice rules for writing stories", "keywords": [ "eslint", diff --git a/code/lib/react-dom-shim/package.json b/code/lib/react-dom-shim/package.json index 3e43ccb706ba..f485171f4967 100644 --- a/code/lib/react-dom-shim/package.json +++ b/code/lib/react-dom-shim/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-dom-shim", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "", "keywords": [ "storybook" diff --git a/code/package.json b/code/package.json index 16a5404a0206..01f43cf9050d 100644 --- a/code/package.json +++ b/code/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/code", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "private": true, "description": "Storybook root", "homepage": "https://storybook.js.org/", diff --git a/code/presets/create-react-app/package.json b/code/presets/create-react-app/package.json index 32d8c0c9139d..fd61d9120d3f 100644 --- a/code/presets/create-react-app/package.json +++ b/code/presets/create-react-app/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-create-react-app", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for Create React App preset", "keywords": [ "storybook" diff --git a/code/presets/react-webpack/package.json b/code/presets/react-webpack/package.json index 748f9c6506ef..1048b0b4e2b6 100644 --- a/code/presets/react-webpack/package.json +++ b/code/presets/react-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-react-webpack", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for React: Develop React Component in isolation with Hot Reloading", "keywords": [ "storybook" diff --git a/code/presets/server-webpack/package.json b/code/presets/server-webpack/package.json index 20b8ffa4bd37..333b72f18eba 100644 --- a/code/presets/server-webpack/package.json +++ b/code/presets/server-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-server-webpack", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/renderers/html/package.json b/code/renderers/html/package.json index 133c97f3a7cd..2ca19ef9b144 100644 --- a/code/renderers/html/package.json +++ b/code/renderers/html/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook HTML renderer: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/renderers/preact/package.json b/code/renderers/preact/package.json index cf9d4f38c94b..53bdf7a41b1c 100644 --- a/code/renderers/preact/package.json +++ b/code/renderers/preact/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook Preact renderer: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/renderers/react/package.json b/code/renderers/react/package.json index c38a4bdf9e95..549506d071db 100644 --- a/code/renderers/react/package.json +++ b/code/renderers/react/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook React renderer", "keywords": [ "storybook" diff --git a/code/renderers/react/template/stories/test-fn.stories.tsx b/code/renderers/react/template/stories/test-fn.stories.tsx index 8901edae6b00..c018fed7a710 100644 --- a/code/renderers/react/template/stories/test-fn.stories.tsx +++ b/code/renderers/react/template/stories/test-fn.stories.tsx @@ -78,7 +78,7 @@ Default.test( }, async ({ canvas }) => { const button = canvas.getByText('Arg from story'); - await expect(button).toBeEnabled(); + await expect(button).not.toHaveAttribute('aria-disabled', 'true'); } ); export const Extended = Default.extend({ @@ -88,5 +88,5 @@ export const Extended = Default.extend({ }); Extended.test('should have extended args', async ({ canvas }) => { const button = canvas.getByText('Arg from extended story'); - await expect(button).toBeEnabled(); + await expect(button).not.toHaveAttribute('aria-disabled', 'true'); }); diff --git a/code/renderers/server/package.json b/code/renderers/server/package.json index fd9a6057d20f..671a36f145f2 100644 --- a/code/renderers/server/package.json +++ b/code/renderers/server/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/server", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook Server renderer: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/renderers/svelte/package.json b/code/renderers/svelte/package.json index 2784edc6395f..92ffa38e6ccd 100644 --- a/code/renderers/svelte/package.json +++ b/code/renderers/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook Svelte renderer: Develop, document, and test UI components in isolation.", "keywords": [ "storybook", diff --git a/code/renderers/vue3/package.json b/code/renderers/vue3/package.json index 3d851884c4ab..e132d50f633b 100644 --- a/code/renderers/vue3/package.json +++ b/code/renderers/vue3/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue3", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook Vue 3 renderer: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/renderers/vue3/src/render.test.ts b/code/renderers/vue3/src/render.test.ts index 98e48b86fd57..f4fb1c38a082 100644 --- a/code/renderers/vue3/src/render.test.ts +++ b/code/renderers/vue3/src/render.test.ts @@ -1,7 +1,9 @@ import { describe, expect, it } from 'vitest'; +import type { Args, Globals } from 'storybook/internal/types'; + import { expectTypeOf } from 'expect-type'; -import { reactive } from 'vue'; +import { computed, reactive } from 'vue'; import { updateArgs } from './render'; @@ -23,7 +25,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', @@ -37,7 +39,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' }); }); @@ -53,7 +55,7 @@ describe('Render Story', () => { }>(); const newArgs = { argFoo: 'foo2', argBar: 'bar2' }; - updateArgs(reactiveArgs, newArgs); + updateArgs(reactiveArgs, newArgs); expect(reactiveArgs).toEqual({ argFoo: 'foo2', @@ -88,4 +90,23 @@ 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 1bc85770371e..b671e0a82861 100644 --- a/code/renderers/vue3/src/render.ts +++ b/code/renderers/vue3/src/render.ts @@ -1,5 +1,11 @@ /* eslint-disable local-rules/no-uncategorized-errors */ -import type { Args, ArgsStoryFn, RenderContext, StoryContext } from 'storybook/internal/types'; +import type { Globals } from 'storybook/internal/types'; +import { + type Args, + type ArgsStoryFn, + type RenderContext, + type StoryContext, +} from 'storybook/internal/types'; import type { PreviewWeb } from 'storybook/preview-api'; import type { App } from 'vue'; @@ -39,6 +45,7 @@ const map = new Map< { vueApp: ReturnType; reactiveArgs: Args; + reactiveGlobals: Globals; } >(); @@ -56,7 +63,8 @@ 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.reactiveArgs, args); + updateArgs(existingApp.reactiveGlobals, storyContext.globals); return () => { teardown(existingApp.vueApp, canvasElement); }; @@ -66,20 +74,19 @@ 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 @@ -141,7 +148,11 @@ function getArgs(element: StoryFnVueReturnType, storyContext: StoryContext(reactiveArgs: T, nextArgs: T) { 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 347ea2b6698e..a5fc3bec526b 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,7 +3,9 @@ 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__; @@ -47,6 +49,62 @@ 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], @@ -84,3 +142,13 @@ 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/code/renderers/web-components/package.json b/code/renderers/web-components/package.json index 5f8083f3a801..6bacf574ff7b 100644 --- a/code/renderers/web-components/package.json +++ b/code/renderers/web-components/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components", - "version": "10.3.0-beta.0", + "version": "10.3.0-beta.1", "description": "Storybook Web Components renderer: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/docs/_snippets/decorator-with-reactive-globals.md b/docs/_snippets/decorator-with-reactive-globals.md new file mode 100644 index 000000000000..12190d20695c --- /dev/null +++ b/docs/_snippets/decorator-with-reactive-globals.md @@ -0,0 +1,52 @@ +```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 new file mode 100644 index 000000000000..3212cae0fc1b --- /dev/null +++ b/docs/_snippets/decorator-with-updateArgs.md @@ -0,0 +1,58 @@ +```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/api/csf/csf-next.mdx b/docs/api/csf/csf-next.mdx index 2c64dc702b3b..46b4f470e53e 100644 --- a/docs/api/csf/csf-next.mdx +++ b/docs/api/csf/csf-next.mdx @@ -271,7 +271,7 @@ PrimaryDisabled.test('should be disabled', async ({ canvas, userEvent, args }) = const button = await canvas.findByRole('button'); await userEvent.click(button); - await expect(button).toBeDisabled(); + await expect(button).toHaveAttribute('aria-disabled', 'true'); await expect(args.onClick).not.toHaveBeenCalled(); }); ``` diff --git a/docs/versions/next.json b/docs/versions/next.json index 33fcbb28b966..dca93cb3ae84 100644 --- a/docs/versions/next.json +++ b/docs/versions/next.json @@ -1 +1 @@ -{"version":"10.3.0-beta.0","info":{"plain":"- Test: Fix clearing mocks in Vitest [#34078](https://github.com/storybookjs/storybook/pull/34078)\n- Core: Fix event source URL based on refId when multiple iframes share the same origin [#34105](https://github.com/storybookjs/storybook/pull/34105)\n- UI: Make TagsFilter state persistent [#33374](https://github.com/storybookjs/storybook/pull/33374)\n- A11y: Make resize handles for addon panel and sidebar accessible [#33980](https://github.com/storybookjs/storybook/pull/33980)\n- Maintenance: Use std-env for AI agent detection in telemetry [#34114](https://github.com/storybookjs/storybook/pull/34114)\n- Addon-Vitest: Make Playwright `--with-deps` platform-aware to avoid `sudo` prompt on Linux [#34121](https://github.com/storybookjs/storybook/pull/34121)\n- Addon-docs: Restore `docs.components` overrides for doc blocks [#34111](https://github.com/storybookjs/storybook/pull/34111)\n- Maintenance: Support vite-plugin-svelte7 which supports Vite 8 [#34115](https://github.com/storybookjs/storybook/pull/34115)\n- Core: Fix handling complex viewport sizes [#33615](https://github.com/storybookjs/storybook/pull/33615)\n- Core: Fix iframe reference for composed Storybook on a subpath [#34100](https://github.com/storybookjs/storybook/pull/34100)\n- Manifest: Rename `experimentalComponentsManifest` → `componentsManifest`, default to `true` [#33974](https://github.com/storybookjs/storybook/pull/33974)\n- Manifests: Fix Attached MDX causing wrong component entries [#34101](https://github.com/storybookjs/storybook/pull/34101)\n- Vue3-Vite: Allow paths in docgen tsconfig option [#32310](https://github.com/storybookjs/storybook/pull/32310), thanks @Thomaash!\n- Next.js: Move image configuration from FrameworkOptions to parameters [#32639](https://github.com/storybookjs/storybook/pull/32639), thanks @y-hsgw!\n- Docs: Make CSS ordering in DocsContainer more predictable [#34015](https://github.com/storybookjs/storybook/pull/34015)"}} \ No newline at end of file +{"version":"10.3.0-beta.1","info":{"plain":"- Addon-Docs: Add React as optimizeDeps entry - [#34176](https://github.com/storybookjs/storybook/pull/34176), thanks @valentinpalkovic!\n- CLI: Avoid hanging of postinstall during init - [#34175](https://github.com/storybookjs/storybook/pull/34175), thanks @valentinpalkovic!"}} \ No newline at end of file diff --git a/docs/writing-stories/decorators.mdx b/docs/writing-stories/decorators.mdx index 2a31268544d9..3b34dd145868 100644 --- a/docs/writing-stories/decorators.mdx +++ b/docs/writing-stories/decorators.mdx @@ -79,6 +79,30 @@ 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. diff --git a/docs/writing-tests/interaction-testing.mdx b/docs/writing-tests/interaction-testing.mdx index 0368b1818eab..cbbb4b535f38 100644 --- a/docs/writing-tests/interaction-testing.mdx +++ b/docs/writing-tests/interaction-testing.mdx @@ -128,7 +128,7 @@ The `expect` utility here combines the methods available in [Vitest’s `expect` | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | [`toBeInTheDocument()`](https://github.com/testing-library/jest-dom#tobeinthedocument) | Checks if the element is in the DOM
`await expect().toBeInTheDocument()` | | [`toBeVisible()`](https://github.com/testing-library/jest-dom#tobevisible) | Checks if the element is visible to the user
`await expect().toBeVisible()` | -| [`toBeDisabled()`](https://github.com/testing-library/jest-dom#tobedisabled) | Checks if an element is disabled
`await expect().toBeDisabled()` | +| [`toHaveAttribute()`](https://github.com/testing-library/jest-dom#tohaveattribute) | Checks if an element has an attribute
`await expect().toHaveAttribute('aria-disabled', 'true')` | | [`toHaveBeenCalled()`](https://vitest.dev/api/expect.html#tohavebeencalled) | Checks that a spied function was called
`await expect().toHaveBeenCalled()` | | [`toHaveBeenCalledWith()`](https://vitest.dev/api/expect.html#tohavebeencalledwith) | Checks that a spied function was called with specific parameters
`await expect().toHaveBeenCalledWith('example')` | diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts index 59918515927c..cbbc78d6e818 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts @@ -237,12 +237,12 @@ test.describe("component testing", () => { const watchModeButton = await page.getByRole("switch", { name: "Watch mode", }); - await expect(runTestsButton).toBeEnabled(); - await expect(watchModeButton).toBeEnabled(); + await expect(runTestsButton).not.toHaveAttribute("aria-disabled", "true"); + await expect(watchModeButton).not.toHaveAttribute("aria-disabled", "true"); await runTestsButton.click(); - await expect(watchModeButton).toBeDisabled(); + await expect(watchModeButton).toHaveAttribute("aria-disabled", "true"); // Wait for test results to appear await expect(page.locator("#testing-module-description")).toHaveText( @@ -250,8 +250,8 @@ test.describe("component testing", () => { { timeout: 30000 } ); - await expect(runTestsButton).toBeEnabled(); - await expect(watchModeButton).toBeEnabled(); + await expect(runTestsButton).not.toHaveAttribute("aria-disabled", "true"); + await expect(watchModeButton).not.toHaveAttribute("aria-disabled", "true"); const errorFilter = page.getByLabel( /Filter main navigation to show \d+ tests with errors/ diff --git a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts index 2c9f06c4f4c6..9c8770975d66 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts @@ -233,12 +233,13 @@ test.describe("component testing", () => { const watchModeButton = await page.getByRole("switch", { name: "Watch mode", }); - await expect(runTestsButton).toBeEnabled(); - await expect(watchModeButton).toBeEnabled(); + await expect(runTestsButton).not.toHaveAttribute("aria-disabled", "true"); + await expect(watchModeButton).not.toHaveAttribute("aria-disabled", "true"); await runTestsButton.click(); - await expect(watchModeButton).toBeDisabled(); + // The test button will be disabled as tests are running + expect(watchModeButton).toHaveAttribute("aria-disabled", "true"), // Wait for test results to appear await expect(page.locator("#testing-module-description")).toHaveText( @@ -246,8 +247,8 @@ test.describe("component testing", () => { { timeout: 30000 } ); - await expect(runTestsButton).toBeEnabled(); - await expect(watchModeButton).toBeEnabled(); + await expect(runTestsButton).not.toHaveAttribute("aria-disabled", "true"); + await expect(watchModeButton).not.toHaveAttribute("aria-disabled", "true"); const errorFilter = page.getByLabel( /Filter main navigation to show \d+ tests with errors/