From a28553fad762a952d09bb3c986e655769e187096 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Thu, 10 Oct 2024 19:15:24 +0200 Subject: [PATCH] support globals and viewports global config in portable stories --- code/.storybook/preview.tsx | 2 - .../test/src/vitest-plugin/test-utils.ts | 2 +- .../test/src/vitest-plugin/viewports.test.ts | 357 +++++++++++++----- .../test/src/vitest-plugin/viewports.ts | 29 +- .../components/tabs/tabs.stories.tsx | 4 - .../components/sidebar/Sidebar.stories.tsx | 2 - .../components/sidebar/Tree.stories.tsx | 16 +- .../modules/store/csf/portable-stories.ts | 14 +- code/core/src/types/modules/composedStory.ts | 2 + 9 files changed, 315 insertions(+), 113 deletions(-) diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index b7e2d5b4948a..60135ac5a8c5 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -18,8 +18,6 @@ import { DocsContext } from '@storybook/blocks'; import { global } from '@storybook/global'; import type { Decorator, Loader, ReactRenderer } from '@storybook/react'; -import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport'; - import { DocsPageWrapper } from '../lib/blocks/src/components'; import { isChromatic } from './isChromatic'; diff --git a/code/addons/test/src/vitest-plugin/test-utils.ts b/code/addons/test/src/vitest-plugin/test-utils.ts index 632ace561619..cdd199d3998b 100644 --- a/code/addons/test/src/vitest-plugin/test-utils.ts +++ b/code/addons/test/src/vitest-plugin/test-utils.ts @@ -25,7 +25,7 @@ export const testStory = ( const _task = context.task as RunnerTask & { meta: TaskMeta & { storyId: string } }; _task.meta.storyId = composedStory.id; - await setViewport(composedStory.parameters.viewport); + await setViewport(composedStory.parameters, composedStory.globals); await composedStory.run(); }; }; diff --git a/code/addons/test/src/vitest-plugin/viewports.test.ts b/code/addons/test/src/vitest-plugin/viewports.test.ts index a175077cf649..ba39f0dc1140 100644 --- a/code/addons/test/src/vitest-plugin/viewports.test.ts +++ b/code/addons/test/src/vitest-plugin/viewports.test.ts @@ -5,7 +5,12 @@ import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport'; import { page } from '@vitest/browser/context'; -import { DEFAULT_VIEWPORT_DIMENSIONS, type ViewportsParam, setViewport } from './viewports'; +import { + DEFAULT_VIEWPORT_DIMENSIONS, + type ViewportsGlobal, + type ViewportsParam, + setViewport, +} from './viewports'; vi.mock('@vitest/browser/context', () => ({ page: { @@ -30,121 +35,286 @@ describe('setViewport', () => { expect(page.viewport).not.toHaveBeenCalled(); }); - it('should fall back to DEFAULT_VIEWPORT_DIMENSIONS if defaultViewport does not exist', async () => { - const viewportsParam: any = { - defaultViewport: 'nonExistentViewport', - }; + describe('globals API', () => { + it('should fall back to DEFAULT_VIEWPORT_DIMENSIONS if selected viewport does not exist', async () => { + const viewportsGlobal: ViewportsGlobal = { + value: 'nonExistentViewport', + }; - await setViewport(viewportsParam); - expect(page.viewport).toHaveBeenCalledWith( - DEFAULT_VIEWPORT_DIMENSIONS.width, - DEFAULT_VIEWPORT_DIMENSIONS.height - ); - }); + await setViewport({}, { viewport: viewportsGlobal }); + expect(page.viewport).toHaveBeenCalledWith( + DEFAULT_VIEWPORT_DIMENSIONS.width, + DEFAULT_VIEWPORT_DIMENSIONS.height + ); + }); - it('should set the dimensions of viewport from INITIAL_VIEWPORTS', async () => { - const viewportsParam: any = { - viewports: INITIAL_VIEWPORTS, - // supported by default in addon viewports - defaultViewport: 'ipad', - }; + it('should fall back to DEFAULT_VIEWPORT_DIMENSIONS if viewport is disabled, even if a viewport is set', async () => { + const viewportsParam: ViewportsParam = { + options: INITIAL_VIEWPORTS, + disable: true, + }; + const viewportsGlobal: ViewportsGlobal = { + value: 'ipad', + }; - await setViewport(viewportsParam); - expect(page.viewport).toHaveBeenCalledWith(768, 1024); - }); + await setViewport({ viewport: viewportsParam }, { viewport: viewportsGlobal }); + expect(page.viewport).toHaveBeenCalledWith( + DEFAULT_VIEWPORT_DIMENSIONS.width, + DEFAULT_VIEWPORT_DIMENSIONS.height + ); + }); + + it('should set the dimensions of viewport from INITIAL_VIEWPORTS', async () => { + const viewportsParam: ViewportsParam = { + options: INITIAL_VIEWPORTS, + }; + const viewportsGlobal: ViewportsGlobal = { + // supported by default in addon viewports + value: 'ipad', + }; - it('should set custom defined viewport dimensions', async () => { - const viewportsParam: ViewportsParam = { - defaultViewport: 'customViewport', - viewports: { - customViewport: { - name: 'Custom Viewport', - type: 'mobile', - styles: { - width: '800px', - height: '600px', + await setViewport({ viewport: viewportsParam }, { viewport: viewportsGlobal }); + expect(page.viewport).toHaveBeenCalledWith(768, 1024); + }); + + it('should set custom defined viewport dimensions', async () => { + const viewportsParam: ViewportsParam = { + options: { + customViewport: { + name: 'Custom Viewport', + type: 'mobile', + styles: { + width: '800px', + height: '600px', + }, }, }, - }, - }; + }; + const viewportsGlobal: ViewportsGlobal = { + value: 'customViewport', + }; - await setViewport(viewportsParam); - expect(page.viewport).toHaveBeenCalledWith(800, 600); - }); + await setViewport({ viewport: viewportsParam }, { viewport: viewportsGlobal }); + expect(page.viewport).toHaveBeenCalledWith(800, 600); + }); - it('should correctly handle percentage-based dimensions', async () => { - const viewportsParam: ViewportsParam = { - defaultViewport: 'percentageViewport', - viewports: { - percentageViewport: { - name: 'Percentage Viewport', - type: 'desktop', - styles: { - width: '50%', - height: '50%', + it('should correctly handle percentage-based dimensions', async () => { + const viewportsParam: ViewportsParam = { + options: { + percentageViewport: { + name: 'Percentage Viewport', + type: 'desktop', + styles: { + width: '50%', + height: '50%', + }, }, }, - }, - }; + }; + const viewportsGlobal: ViewportsGlobal = { + value: 'percentageViewport', + }; - await setViewport(viewportsParam); - expect(page.viewport).toHaveBeenCalledWith(600, 450); // 50% of 1920 and 1080 - }); + await setViewport({ viewport: viewportsParam }, { viewport: viewportsGlobal }); + expect(page.viewport).toHaveBeenCalledWith(600, 450); // 50% of 1920 and 1080 + }); - it('should correctly handle vw and vh based dimensions', async () => { - const viewportsParam: ViewportsParam = { - defaultViewport: 'viewportUnits', - viewports: { - viewportUnits: { - name: 'VW/VH Viewport', - type: 'desktop', - styles: { - width: '50vw', - height: '50vh', + it('should correctly handle vw and vh based dimensions', async () => { + const viewportsParam: ViewportsParam = { + options: { + viewportUnits: { + name: 'VW/VH Viewport', + type: 'desktop', + styles: { + width: '50vw', + height: '50vh', + }, }, }, - }, - }; + }; + const viewportsGlobal: ViewportsGlobal = { + value: 'viewportUnits', + }; - await setViewport(viewportsParam); - expect(page.viewport).toHaveBeenCalledWith(600, 450); // 50% of 1920 and 1080 - }); + await setViewport({ viewport: viewportsParam }, { viewport: viewportsGlobal }); + expect(page.viewport).toHaveBeenCalledWith(600, 450); // 50% of 1920 and 1080 + }); + + it('should correctly handle em based dimensions', async () => { + const viewportsParam: ViewportsParam = { + options: { + viewportUnits: { + name: 'em/rem Viewport', + type: 'mobile', + styles: { + width: '20em', + height: '40rem', + }, + }, + }, + }; + const viewportsGlobal: ViewportsGlobal = { + value: 'viewportUnits', + }; + + await setViewport({ viewport: viewportsParam }, { viewport: viewportsGlobal }); + expect(page.viewport).toHaveBeenCalledWith(320, 640); // dimensions * 16 + }); - it('should correctly handle em based dimensions', async () => { - const viewportsParam: ViewportsParam = { - defaultViewport: 'viewportUnits', - viewports: { - viewportUnits: { - name: 'em/rem Viewport', - type: 'mobile', - styles: { - width: '20em', - height: '40rem', + it('should throw an error for unsupported dimension values', async () => { + const viewportsParam: ViewportsParam = { + options: { + invalidViewport: { + name: 'Invalid Viewport', + type: 'desktop', + styles: { + width: 'calc(100vw - 20px)', + height: '10pc', + }, }, }, - }, - }; + }; + const viewportsGlobal: ViewportsGlobal = { + value: 'invalidViewport', + }; - await setViewport(viewportsParam); - expect(page.viewport).toHaveBeenCalledWith(320, 640); // dimensions * 16 + await expect(setViewport({ viewport: viewportsParam }, { viewport: viewportsGlobal })).rejects + .toThrowErrorMatchingInlineSnapshot(` + [SB_ADDON_VITEST_0001 (UnsupportedViewportDimensionError): Encountered an unsupported value "calc(100vw - 20px)" when setting the viewport width dimension. + + The Storybook plugin only supports values in the following units: + - px, vh, vw, em, rem and %. + + You can either change the viewport for this story to use one of the supported units or skip the test by adding '!test' to the story's tags per https://storybook.js.org/docs/writing-stories/tags] + `); + expect(page.viewport).not.toHaveBeenCalled(); + }); }); - it('should throw an error for unsupported dimension values', async () => { - const viewportsParam: ViewportsParam = { - defaultViewport: 'invalidViewport', - viewports: { - invalidViewport: { - name: 'Invalid Viewport', - type: 'desktop', - styles: { - width: 'calc(100vw - 20px)', - height: '10pc', + describe('parameters API (legacy)', () => { + it('should no op outside when not in Vitest browser mode', async () => { + globalThis.__vitest_browser__ = false; + + await setViewport(); + expect(page.viewport).not.toHaveBeenCalled(); + }); + + it('should fall back to DEFAULT_VIEWPORT_DIMENSIONS if defaultViewport does not exist', async () => { + const viewportsParam: any = { + defaultViewport: 'nonExistentViewport', + }; + + await setViewport({ viewport: viewportsParam }); + expect(page.viewport).toHaveBeenCalledWith( + DEFAULT_VIEWPORT_DIMENSIONS.width, + DEFAULT_VIEWPORT_DIMENSIONS.height + ); + }); + + it('should set the dimensions of viewport from INITIAL_VIEWPORTS', async () => { + const viewportsParam: any = { + viewports: INITIAL_VIEWPORTS, + // supported by default in addon viewports + defaultViewport: 'ipad', + }; + + await setViewport({ viewport: viewportsParam }); + expect(page.viewport).toHaveBeenCalledWith(768, 1024); + }); + + it('should set custom defined viewport dimensions', async () => { + const viewportsParam: ViewportsParam = { + defaultViewport: 'customViewport', + viewports: { + customViewport: { + name: 'Custom Viewport', + type: 'mobile', + styles: { + width: '800px', + height: '600px', + }, + }, + }, + }; + + await setViewport({ viewport: viewportsParam }); + expect(page.viewport).toHaveBeenCalledWith(800, 600); + }); + + it('should correctly handle percentage-based dimensions', async () => { + const viewportsParam: ViewportsParam = { + defaultViewport: 'percentageViewport', + viewports: { + percentageViewport: { + name: 'Percentage Viewport', + type: 'desktop', + styles: { + width: '50%', + height: '50%', + }, + }, + }, + }; + + await setViewport({ viewport: viewportsParam }); + expect(page.viewport).toHaveBeenCalledWith(600, 450); // 50% of 1920 and 1080 + }); + + it('should correctly handle vw and vh based dimensions', async () => { + const viewportsParam: ViewportsParam = { + defaultViewport: 'viewportUnits', + viewports: { + viewportUnits: { + name: 'VW/VH Viewport', + type: 'desktop', + styles: { + width: '50vw', + height: '50vh', + }, + }, + }, + }; + + await setViewport({ viewport: viewportsParam }); + expect(page.viewport).toHaveBeenCalledWith(600, 450); // 50% of 1920 and 1080 + }); + + it('should correctly handle em based dimensions', async () => { + const viewportsParam: ViewportsParam = { + defaultViewport: 'viewportUnits', + viewports: { + viewportUnits: { + name: 'em/rem Viewport', + type: 'mobile', + styles: { + width: '20em', + height: '40rem', + }, }, }, - }, - }; + }; - await expect(setViewport(viewportsParam)).rejects.toThrowErrorMatchingInlineSnapshot(` + await setViewport({ viewport: viewportsParam }); + expect(page.viewport).toHaveBeenCalledWith(320, 640); // dimensions * 16 + }); + + it('should throw an error for unsupported dimension values', async () => { + const viewportsParam: ViewportsParam = { + defaultViewport: 'invalidViewport', + viewports: { + invalidViewport: { + name: 'Invalid Viewport', + type: 'desktop', + styles: { + width: 'calc(100vw - 20px)', + height: '10pc', + }, + }, + }, + }; + + await expect(setViewport({ viewport: viewportsParam })).rejects + .toThrowErrorMatchingInlineSnapshot(` [SB_ADDON_VITEST_0001 (UnsupportedViewportDimensionError): Encountered an unsupported value "calc(100vw - 20px)" when setting the viewport width dimension. The Storybook plugin only supports values in the following units: @@ -152,6 +322,7 @@ describe('setViewport', () => { You can either change the viewport for this story to use one of the supported units or skip the test by adding '!test' to the story's tags per https://storybook.js.org/docs/writing-stories/tags] `); - expect(page.viewport).not.toHaveBeenCalled(); + expect(page.viewport).not.toHaveBeenCalled(); + }); }); }); diff --git a/code/addons/test/src/vitest-plugin/viewports.ts b/code/addons/test/src/vitest-plugin/viewports.ts index c779eb3a6633..0b367893cf9f 100644 --- a/code/addons/test/src/vitest-plugin/viewports.ts +++ b/code/addons/test/src/vitest-plugin/viewports.ts @@ -1,6 +1,8 @@ /* eslint-disable no-underscore-dangle */ import { UnsupportedViewportDimensionError } from 'storybook/internal/preview-errors'; +import type { Globals, Parameters } from '@storybook/csf'; + import { page } from '@vitest/browser/context'; import { MINIMAL_VIEWPORTS } from '../../../viewport/src/defaults'; @@ -12,8 +14,16 @@ declare global { } export interface ViewportsParam { - defaultViewport: string; - viewports: ViewportMap; + defaultViewport?: string; + viewports?: ViewportMap; + options?: ViewportMap; + disable?: boolean; + disabled?: boolean; +} + +export interface ViewportsGlobal { + value?: string; + disable?: boolean; } export const DEFAULT_VIEWPORT_DIMENSIONS = { @@ -47,8 +57,18 @@ const parseDimension = (value: string, dimension: 'width' | 'height') => { } }; -export const setViewport = async (viewportsParam: ViewportsParam = {} as ViewportsParam) => { - const defaultViewport = viewportsParam.defaultViewport; +export const setViewport = async (parameters: Parameters = {}, globals: Globals = {}) => { + let defaultViewport; + const viewportsParam: ViewportsParam = parameters.viewport ?? {}; + const viewportsGlobal: ViewportsGlobal = globals.viewport ?? {}; + const isDisabled = viewportsParam.disable || viewportsParam.disabled; + + // Support new setting from globals, else use the one from parameters + if (viewportsGlobal.value && !isDisabled) { + defaultViewport = viewportsGlobal.value; + } else if (!isDisabled) { + defaultViewport = viewportsParam.defaultViewport; + } if (!page || !globalThis.__vitest_browser__) { return; @@ -57,6 +77,7 @@ export const setViewport = async (viewportsParam: ViewportsParam = {} as Viewpor const viewports = { ...MINIMAL_VIEWPORTS, ...viewportsParam.viewports, + ...viewportsParam.options, }; let viewportWidth = DEFAULT_VIEWPORT_DIMENSIONS.width; diff --git a/code/core/src/components/components/tabs/tabs.stories.tsx b/code/core/src/components/components/tabs/tabs.stories.tsx index b45d82ed8529..f70565a21029 100644 --- a/code/core/src/components/components/tabs/tabs.stories.tsx +++ b/code/core/src/components/components/tabs/tabs.stories.tsx @@ -178,8 +178,6 @@ const customViewports = { }; export const StatefulDynamicWithOpenTooltip = { - // TODO VITEST INTEGRATION: remove this when we support new viewport global format in the vitest integration - tags: ['!vitest'], parameters: { viewport: { options: customViewports, @@ -224,8 +222,6 @@ export const StatefulDynamicWithOpenTooltip = { export const StatefulDynamicWithSelectedAddon = { ...StatefulDynamicWithOpenTooltip, - // TODO VITEST INTEGRATION: remove this when we support new viewport global format in the vitest integration - tags: ['!vitest'], play: async (context) => { await StatefulDynamicWithOpenTooltip.play(context); const canvas = within(context.canvasElement); diff --git a/code/core/src/manager/components/sidebar/Sidebar.stories.tsx b/code/core/src/manager/components/sidebar/Sidebar.stories.tsx index 410403971002..fa08b9ced1b2 100644 --- a/code/core/src/manager/components/sidebar/Sidebar.stories.tsx +++ b/code/core/src/manager/components/sidebar/Sidebar.stories.tsx @@ -179,8 +179,6 @@ export const WithRefsNarrow: Story = { value: 'narrow', }, }, - // TODO VITEST INTEGRATION: remove this when we support new viewport global format in the vitest integration - tags: ['!vitest'], }; export const LoadingWithRefs: Story = { diff --git a/code/core/src/manager/components/sidebar/Tree.stories.tsx b/code/core/src/manager/components/sidebar/Tree.stories.tsx index aed8dbd4648c..a8f3a227b2dc 100644 --- a/code/core/src/manager/components/sidebar/Tree.stories.tsx +++ b/code/core/src/manager/components/sidebar/Tree.stories.tsx @@ -208,7 +208,21 @@ export const DocsOnlySingleStoryComponents = { export const SkipToCanvasLinkFocused: Story = { ...DocsOnlySingleStoryComponents, parameters: { - chromatic: { disable: true }, + chromatic: { viewports: [1280] }, + viewport: { + options: { + desktop: { + name: 'Desktop', + styles: { + width: '100%', + height: '100%', + }, + }, + }, + }, + }, + globals: { + viewport: { value: 'desktop' }, }, play: async ({ canvasElement }) => { const screen = await within(canvasElement); diff --git a/code/core/src/preview-api/modules/store/csf/portable-stories.ts b/code/core/src/preview-api/modules/store/csf/portable-stories.ts index 2ae9143a1a5d..b9efd7da9792 100644 --- a/code/core/src/preview-api/modules/store/csf/portable-stories.ts +++ b/code/core/src/preview-api/modules/store/csf/portable-stories.ts @@ -139,16 +139,17 @@ export function composeStory { const context: StoryContext = prepareContext({ hooks: new HooksContext(), - globals: { - // TODO: remove loading from globalTypes in 9.0 - ...globalsFromGlobalTypes, - ...normalizedProjectAnnotations.initialGlobals, - ...story.storyGlobals, - }, + globals, args: { ...story.initialArgs }, viewMode: 'story', loaded: {}, @@ -251,6 +252,7 @@ export function composeStory, parameters: story.parameters as Parameters, argTypes: story.argTypes as StrictArgTypes, diff --git a/code/core/src/types/modules/composedStory.ts b/code/core/src/types/modules/composedStory.ts index 81ef2ef58c98..7f8d52add055 100644 --- a/code/core/src/types/modules/composedStory.ts +++ b/code/core/src/types/modules/composedStory.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import type { + Globals, ProjectAnnotations, Renderer, StoryContext, @@ -49,6 +50,7 @@ export type ComposedStoryFn< parameters: Parameters; argTypes: StrictArgTypes; tags: Tag[]; + globals: Globals; }; /** * Based on a module of stories, it returns all stories within it, filtering non-stories Each story