From e9d31689ba7791cc3b2b03cc348edff0aa8805ed Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Thu, 11 Jan 2024 09:30:05 -0500 Subject: [PATCH 01/17] Add API function to get versioned docs URL --- code/lib/manager-api/src/modules/versions.ts | 25 +++++ .../manager-api/src/tests/versions.test.js | 98 +++++++++++++++++++ 2 files changed, 123 insertions(+) diff --git a/code/lib/manager-api/src/modules/versions.ts b/code/lib/manager-api/src/modules/versions.ts index a230da3a501f..d17def655cc2 100644 --- a/code/lib/manager-api/src/modules/versions.ts +++ b/code/lib/manager-api/src/modules/versions.ts @@ -36,6 +36,12 @@ export interface SubAPI { * @returns {API_Version} The latest version of the Storybook Manager. */ getLatestVersion: () => API_Version; + /** + * Returns the URL of the Storybook documentation for the current version. + * + * @returns {string} The URL of the Storybook Manager documentation. + */ + getVersionDocsBaseUrl: () => string; /** * Checks if an update is available for the Storybook Manager. * @@ -73,6 +79,25 @@ export const init: ModuleFn = ({ store }) => { } return latest as API_Version; }, + getVersionDocsBaseUrl: () => { + const { + versions: { latest, current }, + } = store.getState(); + + if (!current?.version || !latest?.version) { + return 'https://storybook.js.org/docs/'; + } + + const versionDiff = semver.diff(latest.version, current.version); + + const isLatestDocs = versionDiff === 'patch' || versionDiff === null; + + return isLatestDocs + ? 'https://storybook.js.org/docs/' + : `https://storybook.js.org/docs/${semver.major(current.version)}.${semver.minor( + current.version + )}/`; + }, versionUpdateAvailable: () => { const latest = api.getLatestVersion(); const current = api.getCurrentVersion(); diff --git a/code/lib/manager-api/src/tests/versions.test.js b/code/lib/manager-api/src/tests/versions.test.js index 4f3e1cf93d7f..d23b2e5d045d 100644 --- a/code/lib/manager-api/src/tests/versions.test.js +++ b/code/lib/manager-api/src/tests/versions.test.js @@ -122,6 +122,104 @@ describe('versions API', () => { }); }); + describe('METHOD: getVersionDocsBaseUrl()', () => { + it('returns the latest url when current version is latest', async () => { + const store = createMockStore(); + const { + init, + api, + state: initialState, + } = initVersions({ + store, + }); + + await init(); + + store.setState({ + ...initialState, + versions: { + ...initialState.versions, + current: { version: '7.6.1' }, + latest: { version: '7.6.1' }, + }, + }); + + expect(api.getVersionDocsBaseUrl()).toEqual('https://storybook.js.org/docs/'); + }); + + it('returns the latest url when version has patch diff with latest', async () => { + const store = createMockStore(); + const { + init, + api, + state: initialState, + } = initVersions({ + store, + }); + + await init(); + + store.setState({ + ...initialState, + versions: { + ...initialState.versions, + current: { version: '7.6.1' }, + latest: { version: '7.6.10' }, + }, + }); + + expect(api.getVersionDocsBaseUrl()).toEqual('https://storybook.js.org/docs/'); + }); + + it('returns the versioned url when current has different docs to latest', async () => { + const store = createMockStore(); + const { + init, + api, + state: initialState, + } = initVersions({ + store, + }); + + await init(); + + store.setState({ + ...initialState, + versions: { + ...initialState.versions, + current: { version: '7.2.5' }, + latest: { version: '7.6.10' }, + }, + }); + + expect(api.getVersionDocsBaseUrl()).toEqual('https://storybook.js.org/docs/7.2/'); + }); + + it('returns the versioned url when current is a prerelease', async () => { + const store = createMockStore(); + const { + init, + api, + state: initialState, + } = initVersions({ + store, + }); + + await init(); + + store.setState({ + ...initialState, + versions: { + ...initialState.versions, + current: { version: '8.0.0-beta' }, + latest: { version: '7.6.10' }, + }, + }); + + expect(api.getVersionDocsBaseUrl()).toEqual('https://storybook.js.org/docs/8.0/'); + }); + }); + describe('versionUpdateAvailable', () => { it('matching version', async () => { const store = createMockStore(); From be3ae370e07d7df392079b86fbd6d544b0c4962e Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Thu, 11 Jan 2024 09:30:28 -0500 Subject: [PATCH 02/17] Add docs link to sidebar menu; Fix left icons --- code/ui/manager/src/container/Menu.tsx | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/code/ui/manager/src/container/Menu.tsx b/code/ui/manager/src/container/Menu.tsx index 2c8fb64c01a3..3ebc802b902b 100644 --- a/code/ui/manager/src/container/Menu.tsx +++ b/code/ui/manager/src/container/Menu.tsx @@ -5,7 +5,7 @@ import { Badge } from '@storybook/components'; import type { API, State } from '@storybook/manager-api'; import { shortcutToHumanString } from '@storybook/manager-api'; import { styled, useTheme } from '@storybook/theming'; -import { CheckIcon } from '@storybook/icons'; +import { CheckIcon, InfoIcon, ShareAltIcon, WandIcon } from '@storybook/icons'; const focusableUIElements = { storySearchField: 'storybook-explorer-searchfield', @@ -65,10 +65,20 @@ export const useMenu = ( id: 'about', title: 'About your Storybook', onClick: () => api.changeSettingsTab('about'), + icon: , }), [api] ); + const documentation = useMemo(() => { + return { + id: 'documentation', + title: 'Documentation', + href: api.getVersionDocsBaseUrl(), + icon: , + }; + }, [api]); + const whatsNewNotificationsEnabled = state.whatsNewData?.status === 'SUCCESS' && !state.disableWhatsNewNotifications; const isWhatsNewUnread = api.isWhatsNewUnread(); @@ -80,6 +90,7 @@ export const useMenu = ( right: whatsNewNotificationsEnabled && isWhatsNewUnread && ( Check it out ), + icon: , }), [api, whatsNewNotificationsEnabled, isWhatsNewUnread] ); @@ -104,7 +115,7 @@ export const useMenu = ( onClick: () => api.toggleNav(), active: isNavShown, right: enableShortcuts ? : null, - left: isNavShown ? : null, + icon: isNavShown ? : null, }), [api, enableShortcuts, shortcutKeys, isNavShown] ); @@ -116,7 +127,7 @@ export const useMenu = ( onClick: () => api.toggleToolbar(), active: showToolbar, right: enableShortcuts ? : null, - left: showToolbar ? : null, + icon: showToolbar ? : null, }), [api, enableShortcuts, shortcutKeys, showToolbar] ); @@ -128,7 +139,7 @@ export const useMenu = ( onClick: () => api.togglePanel(), active: isPanelShown, right: enableShortcuts ? : null, - left: isPanelShown ? : null, + icon: isPanelShown ? : null, }), [api, enableShortcuts, shortcutKeys, isPanelShown] ); @@ -150,7 +161,7 @@ export const useMenu = ( onClick: () => api.toggleFullscreen(), active: isFullscreen, right: enableShortcuts ? : null, - left: isFullscreen ? : null, + icon: isFullscreen ? : null, }), [api, enableShortcuts, shortcutKeys, isFullscreen] ); @@ -232,6 +243,7 @@ export const useMenu = ( () => [ about, ...(state.whatsNewData?.status === 'SUCCESS' ? [whatsNew] : []), + documentation, shortcuts, sidebarToggle, toolbarToogle, @@ -250,6 +262,7 @@ export const useMenu = ( about, state, whatsNew, + documentation, shortcuts, sidebarToggle, toolbarToogle, From 4d8f835a4d391babf432b645ed69831d12ce55c6 Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Thu, 11 Jan 2024 09:48:17 -0500 Subject: [PATCH 03/17] Add new empty state to interactions panel --- .../src/components/EmptyState.tsx | 100 ++++++++++++++++++ .../src/components/InteractionsPanel.tsx | 21 ++-- code/addons/interactions/src/constants.ts | 3 + 3 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 code/addons/interactions/src/components/EmptyState.tsx diff --git a/code/addons/interactions/src/components/EmptyState.tsx b/code/addons/interactions/src/components/EmptyState.tsx new file mode 100644 index 000000000000..4713cbb98dcd --- /dev/null +++ b/code/addons/interactions/src/components/EmptyState.tsx @@ -0,0 +1,100 @@ +import type { FC } from 'react'; +import React, { useEffect, useState } from 'react'; +import { styled } from '@storybook/theming'; +import { Link } from '@storybook/components'; +import { DocumentIcon, VideoIcon } from '@storybook/icons'; +import { DOCUMENTATION_LINK, TUTORIAL_VIDEO_LINK } from '../constants'; + +const Wrapper = styled.div(({ theme }) => ({ + height: '100%', + display: 'flex', + padding: 0, + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'column', + gap: 15, + background: theme.background.content, +})); + +const Content = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: 4, + maxWidth: 415, +}); + +const Title = styled.div(({ theme }) => ({ + fontWeight: theme.typography.weight.bold, + fontSize: theme.typography.size.s2 - 1, + textAlign: 'center', + color: theme.textColor, +})); + +const Description = styled.div(({ theme }) => ({ + fontWeight: theme.typography.weight.regular, + fontSize: theme.typography.size.s2 - 1, + textAlign: 'center', + color: theme.textMutedColor, +})); + +const Links = styled.div(({ theme }) => ({ + display: 'flex', + fontSize: theme.typography.size.s2 - 1, + gap: 25, +})); + +const Divider = styled.div(({ theme }) => ({ + width: 1, + height: 16, + backgroundColor: theme.appBorderColor, +})); + +const buildDocsUrl = (base: string, path: string, renderer: string) => + `${base}${path}?renderer=${renderer}`; + +interface EmptyProps { + renderer: string; + docsUrlBase: string; +} + +export const Empty: FC = ({ renderer, docsUrlBase }) => { + const [isLoading, setIsLoading] = useState(true); + + // We are adding a small delay to avoid flickering when the story is loading. + // It takes a bit of time for the controls to appear, so we don't want + // to show the empty state for a split second. + useEffect(() => { + const load = setTimeout(() => { + setIsLoading(false); + }, 100); + + return () => clearTimeout(load); + }, []); + + if (isLoading) return null; + + return ( + + + Interaction testing + + Interaction tests allow you to verify the functional aspects of UIs. Write a play function + for your story and you'll see it run here. + + + + + Watch 8m video + + + + Read docs + + + + ); +}; diff --git a/code/addons/interactions/src/components/InteractionsPanel.tsx b/code/addons/interactions/src/components/InteractionsPanel.tsx index 0663656cc14b..f3120b7c2aa7 100644 --- a/code/addons/interactions/src/components/InteractionsPanel.tsx +++ b/code/addons/interactions/src/components/InteractionsPanel.tsx @@ -1,13 +1,14 @@ import * as React from 'react'; -import { Link, Placeholder } from '@storybook/components'; import { type Call, CallStates, type ControlStates } from '@storybook/instrumenter'; import { styled } from '@storybook/theming'; +import { useStorybookApi } from '@storybook/manager-api'; import { transparentize } from 'polished'; import { Subnav } from './Subnav'; import { Interaction } from './Interaction'; import { isTestAssertionError } from '../utils'; +import { Empty } from './EmptyState'; export interface Controls { start: (args: any) => void; @@ -40,7 +41,7 @@ interface InteractionsPanelProps { } const Container = styled.div(({ theme }) => ({ - minHeight: '100%', + height: '100%', background: theme.background.content, })); @@ -95,6 +96,11 @@ export const InteractionsPanel: React.FC = React.memo( onScrollToEnd, endRef, }) { + const api = useStorybookApi(); + + const renderer = 'react'; + const docsUrlBase = api.getVersionDocsBaseUrl(); + return ( {(interactions.length > 0 || hasException) && ( @@ -154,16 +160,7 @@ export const InteractionsPanel: React.FC = React.memo( )}
{!isPlaying && !caughtException && interactions.length === 0 && ( - - No interactions found - - Learn how to add interactions to your story - - + )} ); diff --git a/code/addons/interactions/src/constants.ts b/code/addons/interactions/src/constants.ts index 3c723e93852c..3d4358712490 100644 --- a/code/addons/interactions/src/constants.ts +++ b/code/addons/interactions/src/constants.ts @@ -1,2 +1,5 @@ export const ADDON_ID = 'storybook/interactions'; export const PANEL_ID = `${ADDON_ID}/panel`; + +export const TUTORIAL_VIDEO_LINK = 'https://youtu.be/Waht9qq7AoA'; +export const DOCUMENTATION_LINK = 'writing-stories/play-function'; From 39e6fd82c4a39766bdfc66f4b67021b57339a58f Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Thu, 11 Jan 2024 09:48:50 -0500 Subject: [PATCH 04/17] Add icons to argstable to match other empty states --- .../blocks/src/components/ArgsTable/ArgsTable.tsx | 4 ++-- code/ui/blocks/src/components/ArgsTable/Empty.tsx | 14 +++++--------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/code/ui/blocks/src/components/ArgsTable/ArgsTable.tsx b/code/ui/blocks/src/components/ArgsTable/ArgsTable.tsx index c0a9e8b0d0bc..70e4e1f0edd7 100644 --- a/code/ui/blocks/src/components/ArgsTable/ArgsTable.tsx +++ b/code/ui/blocks/src/components/ArgsTable/ArgsTable.tsx @@ -7,7 +7,7 @@ import { includeConditionalArg } from '@storybook/csf'; import { once } from '@storybook/client-logger'; import { IconButton, ResetWrapper, Link } from '@storybook/components'; -import { UndoIcon } from '@storybook/icons'; +import { DocumentIcon, UndoIcon } from '@storybook/icons'; import { ArgRow } from './ArgRow'; import { SectionRow } from './SectionRow'; import type { ArgType, ArgTypes, Args, Globals } from './types'; @@ -323,7 +323,7 @@ export const ArgsTable: FC = (props) => { {error}  - Read the docs + Read the docs ); diff --git a/code/ui/blocks/src/components/ArgsTable/Empty.tsx b/code/ui/blocks/src/components/ArgsTable/Empty.tsx index 796a3dcfba39..c4269a605f95 100644 --- a/code/ui/blocks/src/components/ArgsTable/Empty.tsx +++ b/code/ui/blocks/src/components/ArgsTable/Empty.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react'; import React, { useEffect, useState } from 'react'; import { styled } from '@storybook/theming'; import { Link } from '@storybook/components'; -import { VideoIcon } from '@storybook/icons'; +import { DocumentIcon, SupportIcon, VideoIcon } from '@storybook/icons'; interface EmptyProps { inAddonPanel?: boolean; @@ -92,21 +92,17 @@ export const Empty: FC = ({ inAddonPanel }) => { - Read docs + Read docs )} {!inAddonPanel && ( - - Learn how to set that up + + Learn how to set that up )} From bffee5ba08b1c5a7a6440a681b48400b9d55fc23 Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Fri, 12 Jan 2024 11:34:19 -0500 Subject: [PATCH 05/17] WIP: saving progress on renderer --- .../interactions/src/components/EmptyState.tsx | 12 +++++++++++- .../src/components/InteractionsPanel.tsx | 2 +- code/ui/manager/src/container/Menu.tsx | 15 ++++++++++++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/code/addons/interactions/src/components/EmptyState.tsx b/code/addons/interactions/src/components/EmptyState.tsx index 4713cbb98dcd..b74358cfc170 100644 --- a/code/addons/interactions/src/components/EmptyState.tsx +++ b/code/addons/interactions/src/components/EmptyState.tsx @@ -49,8 +49,18 @@ const Divider = styled.div(({ theme }) => ({ backgroundColor: theme.appBorderColor, })); +const sanitizeRendererForDocsUrl = (renderer: string) => { + const normalizedRenderer = renderer.toLowerCase(); + + if (normalizedRenderer.includes('vue')) { + return 'vue'; + } + + return normalizedRenderer; +}; + const buildDocsUrl = (base: string, path: string, renderer: string) => - `${base}${path}?renderer=${renderer}`; + `${base}${path}?renderer=${sanitizeRendererForDocsUrl(renderer)}`; interface EmptyProps { renderer: string; diff --git a/code/addons/interactions/src/components/InteractionsPanel.tsx b/code/addons/interactions/src/components/InteractionsPanel.tsx index f3120b7c2aa7..ddd0e69b6398 100644 --- a/code/addons/interactions/src/components/InteractionsPanel.tsx +++ b/code/addons/interactions/src/components/InteractionsPanel.tsx @@ -98,7 +98,7 @@ export const InteractionsPanel: React.FC = React.memo( }) { const api = useStorybookApi(); - const renderer = 'react'; + const renderer = globalThis.STORYBOOK_ENV; const docsUrlBase = api.getVersionDocsBaseUrl(); return ( diff --git a/code/ui/manager/src/container/Menu.tsx b/code/ui/manager/src/container/Menu.tsx index 3ebc802b902b..ea95618ce520 100644 --- a/code/ui/manager/src/container/Menu.tsx +++ b/code/ui/manager/src/container/Menu.tsx @@ -48,6 +48,16 @@ export const Shortcut: FC<{ keys: string[] }> = ({ keys }) => ( ); +const sanitizeRendererForDocsUrl = (renderer: string) => { + const normalizedRenderer = renderer.toLowerCase(); + + if (normalizedRenderer.includes('vue')) { + return 'vue'; + } + + return normalizedRenderer; +}; + export const useMenu = ( state: State, api: API, @@ -71,10 +81,13 @@ export const useMenu = ( ); const documentation = useMemo(() => { + const baseURL = api.getVersionDocsBaseUrl(); + const renderer = sanitizeRendererForDocsUrl(globalThis.STORYBOOK_ENV); + return { id: 'documentation', title: 'Documentation', - href: api.getVersionDocsBaseUrl(), + href: `${baseURL}?renderer=${renderer}`, icon: , }; }, [api]); From d04198cb6b57c9ec2e1f8f5ebc7714eb9cef253d Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Tue, 16 Jan 2024 10:44:02 -0500 Subject: [PATCH 06/17] Add option to add renderer to url --- .../src/components/EmptyState.tsx | 45 +++++++------------ .../src/components/InteractionsPanel.tsx | 11 +---- code/builders/builder-manager/src/index.ts | 40 ++++++++++++++++- .../builder-manager/src/utils/framework.ts | 25 +++++++++++ .../builder-manager/src/utils/template.ts | 4 +- code/lib/manager-api/src/modules/versions.ts | 33 +++++++++----- .../manager-api/src/tests/versions.test.js | 45 ++++++++++++++++--- code/ui/manager/src/container/Menu.tsx | 5 +-- 8 files changed, 148 insertions(+), 60 deletions(-) create mode 100644 code/builders/builder-manager/src/utils/framework.ts diff --git a/code/addons/interactions/src/components/EmptyState.tsx b/code/addons/interactions/src/components/EmptyState.tsx index b74358cfc170..d4fa62c144a4 100644 --- a/code/addons/interactions/src/components/EmptyState.tsx +++ b/code/addons/interactions/src/components/EmptyState.tsx @@ -1,8 +1,9 @@ -import type { FC } from 'react'; import React, { useEffect, useState } from 'react'; -import { styled } from '@storybook/theming'; import { Link } from '@storybook/components'; import { DocumentIcon, VideoIcon } from '@storybook/icons'; +import { Consumer, useStorybookApi } from '@storybook/manager-api'; +import { styled } from '@storybook/theming'; + import { DOCUMENTATION_LINK, TUTORIAL_VIDEO_LINK } from '../constants'; const Wrapper = styled.div(({ theme }) => ({ @@ -49,26 +50,14 @@ const Divider = styled.div(({ theme }) => ({ backgroundColor: theme.appBorderColor, })); -const sanitizeRendererForDocsUrl = (renderer: string) => { - const normalizedRenderer = renderer.toLowerCase(); - - if (normalizedRenderer.includes('vue')) { - return 'vue'; - } - - return normalizedRenderer; -}; - -const buildDocsUrl = (base: string, path: string, renderer: string) => - `${base}${path}?renderer=${sanitizeRendererForDocsUrl(renderer)}`; - -interface EmptyProps { - renderer: string; - docsUrlBase: string; -} - -export const Empty: FC = ({ renderer, docsUrlBase }) => { +export const Empty = () => { const [isLoading, setIsLoading] = useState(true); + const api = useStorybookApi(); + const docsUrl = api.getDocsUrl({ + subpath: DOCUMENTATION_LINK, + versioned: true, + renderer: true, + }); // We are adding a small delay to avoid flickering when the story is loading. // It takes a bit of time for the controls to appear, so we don't want @@ -97,13 +86,13 @@ export const Empty: FC = ({ renderer, docsUrlBase }) => { Watch 8m video - - Read docs - + + {({ state }) => ( + + Read docs + + )} + ); diff --git a/code/addons/interactions/src/components/InteractionsPanel.tsx b/code/addons/interactions/src/components/InteractionsPanel.tsx index ddd0e69b6398..bf7e0b2f3789 100644 --- a/code/addons/interactions/src/components/InteractionsPanel.tsx +++ b/code/addons/interactions/src/components/InteractionsPanel.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { type Call, CallStates, type ControlStates } from '@storybook/instrumenter'; import { styled } from '@storybook/theming'; -import { useStorybookApi } from '@storybook/manager-api'; import { transparentize } from 'polished'; import { Subnav } from './Subnav'; @@ -9,6 +8,7 @@ import { Subnav } from './Subnav'; import { Interaction } from './Interaction'; import { isTestAssertionError } from '../utils'; import { Empty } from './EmptyState'; +import { DOCUMENTATION_LINK } from '../constants'; export interface Controls { start: (args: any) => void; @@ -96,11 +96,6 @@ export const InteractionsPanel: React.FC = React.memo( onScrollToEnd, endRef, }) { - const api = useStorybookApi(); - - const renderer = globalThis.STORYBOOK_ENV; - const docsUrlBase = api.getVersionDocsBaseUrl(); - return ( {(interactions.length > 0 || hasException) && ( @@ -159,9 +154,7 @@ export const InteractionsPanel: React.FC = React.memo( )}
- {!isPlaying && !caughtException && interactions.length === 0 && ( - - )} + {!isPlaying && !caughtException && interactions.length === 0 && } ); } diff --git a/code/builders/builder-manager/src/index.ts b/code/builders/builder-manager/src/index.ts index ef71811029f4..66520e34b2b6 100644 --- a/code/builders/builder-manager/src/index.ts +++ b/code/builders/builder-manager/src/index.ts @@ -24,6 +24,7 @@ import type { import { getData } from './utils/data'; import { safeResolve } from './utils/safeResolve'; import { readOrderedFiles } from './utils/files'; +import { normalizeBuilderName, rendererPathToName } from './utils/framework'; let compilation: Compilation; let asyncIterator: ReturnType | ReturnType; @@ -163,6 +164,21 @@ const starter: StarterFunction = async function* starterGeneratorFn({ const { cssFiles, jsFiles } = await readOrderedFiles(addonsDir, compilation?.outputFiles); + // Build additional global values + const globals: Record = {}; + + const { renderer: rendererPath, builder: builderValue } = await options.presets.apply('core'); + + const rendererName = rendererPathToName(rendererPath); + if (rendererName) { + globals.STORYBOOK_RENDERER = rendererName; + } + + const builderName = normalizeBuilderName(builderValue); + if (builderName) { + globals.STORYBOOK_BUILDER = builderName; + } + yield; const html = await renderHTML( @@ -177,7 +193,8 @@ const starter: StarterFunction = async function* starterGeneratorFn({ logLevel, docsOptions, tagsOptions, - options + options, + globals ); yield; @@ -252,6 +269,21 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, }); const { cssFiles, jsFiles } = await readOrderedFiles(addonsDir, compilation?.outputFiles); + // Build additional global values + const globals: Record = {}; + + const { renderer: rendererPath, builder: builderValue } = await options.presets.apply('core'); + + const rendererName = rendererPathToName(rendererPath); + if (rendererName) { + globals.STORYBOOK_RENDERER = rendererName; + } + + const builderName = normalizeBuilderName(builderValue); + if (builderName) { + globals.STORYBOOK_BUILDER = builderName; + } + yield; const html = await renderHTML( @@ -266,7 +298,8 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, logLevel, docsOptions, tagsOptions, - options + options, + globals ); await Promise.all([ @@ -317,3 +350,6 @@ export const build: ManagerBuilder['build'] = async (options) => { export const corePresets: ManagerBuilder['corePresets'] = []; export const overridePresets: ManagerBuilder['overridePresets'] = []; +function sanatize(arg0: string | undefined) { + throw new Error('Function not implemented.'); +} diff --git a/code/builders/builder-manager/src/utils/framework.ts b/code/builders/builder-manager/src/utils/framework.ts new file mode 100644 index 000000000000..91c601d79a6d --- /dev/null +++ b/code/builders/builder-manager/src/utils/framework.ts @@ -0,0 +1,25 @@ +import path from 'path'; + +export const rendererPathToName = (rendererPath?: string) => { + const value = rendererPath?.split(path.sep).pop()?.toLowerCase(); + + if (!value) { + return undefined; + } + + if (value.includes('vue')) { + return 'vue'; + } + + return value; +}; + +export const normalizeBuilderName = (builderValue?: string | { name: string }) => { + if (!builderValue) { + return undefined; + } + + const name = typeof builderValue === 'string' ? builderValue : builderValue.name; + + return name.includes('webpack5') ? 'webpack5' : name; +}; diff --git a/code/builders/builder-manager/src/utils/template.ts b/code/builders/builder-manager/src/utils/template.ts index 4ccb2d50864a..2ea7c6dfc85a 100644 --- a/code/builders/builder-manager/src/utils/template.ts +++ b/code/builders/builder-manager/src/utils/template.ts @@ -35,7 +35,8 @@ export const renderHTML = async ( logLevel: Promise, docsOptions: Promise, tagsOptions: Promise, - { versionCheck, previewUrl, configType, ignorePreview }: Options + { versionCheck, previewUrl, configType, ignorePreview }: Options, + globals: Record ) => { const titleRef = await title; const templateRef = await template; @@ -54,6 +55,7 @@ export const renderHTML = async ( VERSIONCHECK: JSON.stringify(JSON.stringify(versionCheck), null, 2), PREVIEW_URL: JSON.stringify(previewUrl, null, 2), // global preview URL TAGS_OPTIONS: JSON.stringify(await tagsOptions, null, 2), + ...globals, }, head: (await customHead) || '', ignorePreview, diff --git a/code/lib/manager-api/src/modules/versions.ts b/code/lib/manager-api/src/modules/versions.ts index d17def655cc2..7564b02919b5 100644 --- a/code/lib/manager-api/src/modules/versions.ts +++ b/code/lib/manager-api/src/modules/versions.ts @@ -41,7 +41,7 @@ export interface SubAPI { * * @returns {string} The URL of the Storybook Manager documentation. */ - getVersionDocsBaseUrl: () => string; + getDocsUrl: (options: { subpath?: string; versioned?: boolean; renderer?: boolean }) => string; /** * Checks if an update is available for the Storybook Manager. * @@ -79,24 +79,35 @@ export const init: ModuleFn = ({ store }) => { } return latest as API_Version; }, - getVersionDocsBaseUrl: () => { + // TODO: Move this to it's own "info" module later + getDocsUrl: ({ subpath, versioned, renderer }) => { const { versions: { latest, current }, } = store.getState(); - if (!current?.version || !latest?.version) { - return 'https://storybook.js.org/docs/'; + let url = 'https://storybook.js.org/docs/'; + + console.log('current', current); + console.log('latest', latest); + + if (versioned && current?.version && latest?.version) { + const versionDiff = semver.diff(latest.version, current.version); + const isLatestDocs = versionDiff === 'patch' || versionDiff === null; + + if (!isLatestDocs) { + url += `${semver.major(current.version)}.${semver.minor(current.version)}/`; + } } - const versionDiff = semver.diff(latest.version, current.version); + if (subpath) { + url += `${subpath}/`; + } - const isLatestDocs = versionDiff === 'patch' || versionDiff === null; + if (renderer && typeof global.STORYBOOK_RENDERER !== 'undefined') { + url += `?renderer=${global.STORYBOOK_RENDERER}`; + } - return isLatestDocs - ? 'https://storybook.js.org/docs/' - : `https://storybook.js.org/docs/${semver.major(current.version)}.${semver.minor( - current.version - )}/`; + return url; }, versionUpdateAvailable: () => { const latest = api.getLatestVersion(); diff --git a/code/lib/manager-api/src/tests/versions.test.js b/code/lib/manager-api/src/tests/versions.test.js index d23b2e5d045d..755b5ed0db8a 100644 --- a/code/lib/manager-api/src/tests/versions.test.js +++ b/code/lib/manager-api/src/tests/versions.test.js @@ -1,4 +1,6 @@ -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { global } from '@storybook/global'; +import exp from 'constants'; import { init as initVersions } from '../modules/versions'; vi.mock('../version', () => ({ @@ -122,7 +124,11 @@ describe('versions API', () => { }); }); - describe('METHOD: getVersionDocsBaseUrl()', () => { + describe('METHOD: getDocsUrl()', () => { + beforeEach(() => { + global.STORYBOOK_RENDERER = undefined; + }); + it('returns the latest url when current version is latest', async () => { const store = createMockStore(); const { @@ -144,7 +150,7 @@ describe('versions API', () => { }, }); - expect(api.getVersionDocsBaseUrl()).toEqual('https://storybook.js.org/docs/'); + expect(api.getDocsUrl({ versioned: true })).toEqual('https://storybook.js.org/docs/'); }); it('returns the latest url when version has patch diff with latest', async () => { @@ -168,7 +174,7 @@ describe('versions API', () => { }, }); - expect(api.getVersionDocsBaseUrl()).toEqual('https://storybook.js.org/docs/'); + expect(api.getDocsUrl({ versioned: true })).toEqual('https://storybook.js.org/docs/'); }); it('returns the versioned url when current has different docs to latest', async () => { @@ -192,7 +198,7 @@ describe('versions API', () => { }, }); - expect(api.getVersionDocsBaseUrl()).toEqual('https://storybook.js.org/docs/7.2/'); + expect(api.getDocsUrl({ versioned: true })).toEqual('https://storybook.js.org/docs/7.2/'); }); it('returns the versioned url when current is a prerelease', async () => { @@ -216,7 +222,34 @@ describe('versions API', () => { }, }); - expect(api.getVersionDocsBaseUrl()).toEqual('https://storybook.js.org/docs/8.0/'); + expect(api.getDocsUrl({ versioned: true })).toEqual('https://storybook.js.org/docs/8.0/'); + }); + + it('returns a Url with a renderer query param when "renderer" is true', async () => { + const store = createMockStore(); + const { + init, + api, + state: initialState, + } = initVersions({ + store, + }); + store.setState({ + ...initialState, + versions: { + ...initialState.versions, + current: { version: '5.2.1' }, + latest: { version: '5.2.1' }, + }, + }); + + await init(); + + global.STORYBOOK_RENDERER = 'vue'; + + expect(api.getDocsUrl({ renderer: true })).toEqual( + 'https://storybook.js.org/docs/?renderer=vue' + ); }); }); diff --git a/code/ui/manager/src/container/Menu.tsx b/code/ui/manager/src/container/Menu.tsx index ea95618ce520..21a3a445c83f 100644 --- a/code/ui/manager/src/container/Menu.tsx +++ b/code/ui/manager/src/container/Menu.tsx @@ -81,13 +81,12 @@ export const useMenu = ( ); const documentation = useMemo(() => { - const baseURL = api.getVersionDocsBaseUrl(); - const renderer = sanitizeRendererForDocsUrl(globalThis.STORYBOOK_ENV); + const docsUrl = api.getDocsUrl({ versioned: true, renderer: true }); return { id: 'documentation', title: 'Documentation', - href: `${baseURL}?renderer=${renderer}`, + href: docsUrl, icon: , }; }, [api]); From 26371a597232a7adb47502051859227e38d23093 Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Tue, 16 Jan 2024 13:33:11 -0500 Subject: [PATCH 07/17] stringify injected globals --- code/builders/builder-manager/src/utils/template.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/code/builders/builder-manager/src/utils/template.ts b/code/builders/builder-manager/src/utils/template.ts index 2ea7c6dfc85a..dd2d1bc88e20 100644 --- a/code/builders/builder-manager/src/utils/template.ts +++ b/code/builders/builder-manager/src/utils/template.ts @@ -40,6 +40,10 @@ export const renderHTML = async ( ) => { const titleRef = await title; const templateRef = await template; + const stringifiedGlobals = Object.entries(globals).reduce( + (transformed, [key, value]) => ({ ...transformed, [key]: JSON.stringify(value) }), + {} + ); return render(templateRef, { title: titleRef ? `${titleRef} - Storybook` : 'Storybook', @@ -55,7 +59,7 @@ export const renderHTML = async ( VERSIONCHECK: JSON.stringify(JSON.stringify(versionCheck), null, 2), PREVIEW_URL: JSON.stringify(previewUrl, null, 2), // global preview URL TAGS_OPTIONS: JSON.stringify(await tagsOptions, null, 2), - ...globals, + ...stringifiedGlobals, }, head: (await customHead) || '', ignorePreview, From 9f23041ee74a58b813ee342560fcfdc185efc513 Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Tue, 16 Jan 2024 15:02:35 -0500 Subject: [PATCH 08/17] Remove unneeded function --- code/builders/builder-manager/src/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/code/builders/builder-manager/src/index.ts b/code/builders/builder-manager/src/index.ts index 66520e34b2b6..aad53cf05895 100644 --- a/code/builders/builder-manager/src/index.ts +++ b/code/builders/builder-manager/src/index.ts @@ -350,6 +350,3 @@ export const build: ManagerBuilder['build'] = async (options) => { export const corePresets: ManagerBuilder['corePresets'] = []; export const overridePresets: ManagerBuilder['overridePresets'] = []; -function sanatize(arg0: string | undefined) { - throw new Error('Function not implemented.'); -} From d355f59f967197cab79f3ab2b22701ea2166d2cc Mon Sep 17 00:00:00 2001 From: Shaun Evening Date: Tue, 16 Jan 2024 15:35:55 -0500 Subject: [PATCH 09/17] Update code/addons/interactions/src/constants.ts Co-authored-by: Kyle Gach --- code/addons/interactions/src/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/addons/interactions/src/constants.ts b/code/addons/interactions/src/constants.ts index 3d4358712490..1b70ededc7c4 100644 --- a/code/addons/interactions/src/constants.ts +++ b/code/addons/interactions/src/constants.ts @@ -2,4 +2,4 @@ export const ADDON_ID = 'storybook/interactions'; export const PANEL_ID = `${ADDON_ID}/panel`; export const TUTORIAL_VIDEO_LINK = 'https://youtu.be/Waht9qq7AoA'; -export const DOCUMENTATION_LINK = 'writing-stories/play-function'; +export const DOCUMENTATION_LINK = 'writing-tests/interaction-testing'; From e17a7c6bb8257e5c6334056d17dd439b08e503e2 Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Wed, 17 Jan 2024 19:16:07 -0500 Subject: [PATCH 10/17] Refactor logic for adding renderer globals --- code/builders/builder-manager/src/index.ts | 30 ++----------- .../builder-manager/src/utils/framework.ts | 42 ++++++++++++------- 2 files changed, 31 insertions(+), 41 deletions(-) diff --git a/code/builders/builder-manager/src/index.ts b/code/builders/builder-manager/src/index.ts index aad53cf05895..28e6fdd5a55e 100644 --- a/code/builders/builder-manager/src/index.ts +++ b/code/builders/builder-manager/src/index.ts @@ -24,7 +24,7 @@ import type { import { getData } from './utils/data'; import { safeResolve } from './utils/safeResolve'; import { readOrderedFiles } from './utils/files'; -import { normalizeBuilderName, rendererPathToName } from './utils/framework'; +import { buildFrameworkGlobalsFromOptions } from './utils/framework'; let compilation: Compilation; let asyncIterator: ReturnType | ReturnType; @@ -165,19 +165,7 @@ const starter: StarterFunction = async function* starterGeneratorFn({ const { cssFiles, jsFiles } = await readOrderedFiles(addonsDir, compilation?.outputFiles); // Build additional global values - const globals: Record = {}; - - const { renderer: rendererPath, builder: builderValue } = await options.presets.apply('core'); - - const rendererName = rendererPathToName(rendererPath); - if (rendererName) { - globals.STORYBOOK_RENDERER = rendererName; - } - - const builderName = normalizeBuilderName(builderValue); - if (builderName) { - globals.STORYBOOK_BUILDER = builderName; - } + const globals: Record = await buildFrameworkGlobalsFromOptions(options); yield; @@ -270,19 +258,7 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, const { cssFiles, jsFiles } = await readOrderedFiles(addonsDir, compilation?.outputFiles); // Build additional global values - const globals: Record = {}; - - const { renderer: rendererPath, builder: builderValue } = await options.presets.apply('core'); - - const rendererName = rendererPathToName(rendererPath); - if (rendererName) { - globals.STORYBOOK_RENDERER = rendererName; - } - - const builderName = normalizeBuilderName(builderValue); - if (builderName) { - globals.STORYBOOK_BUILDER = builderName; - } + const globals: Record = await buildFrameworkGlobalsFromOptions(options); yield; diff --git a/code/builders/builder-manager/src/utils/framework.ts b/code/builders/builder-manager/src/utils/framework.ts index 91c601d79a6d..84941fe360ff 100644 --- a/code/builders/builder-manager/src/utils/framework.ts +++ b/code/builders/builder-manager/src/utils/framework.ts @@ -1,25 +1,39 @@ -import path from 'path'; +import type { Options } from '@storybook/types'; -export const rendererPathToName = (rendererPath?: string) => { - const value = rendererPath?.split(path.sep).pop()?.toLowerCase(); +interface PropertyObject { + name: string; + options?: Record; +} - if (!value) { - return undefined; - } +type Property = string | PropertyObject | undefined; - if (value.includes('vue')) { - return 'vue'; +const pluckNameFromConfigProperty = (property: Property) => { + if (!property) { + return undefined; } - return value; + return typeof property === 'string' ? property : property.name; }; -export const normalizeBuilderName = (builderValue?: string | { name: string }) => { - if (!builderValue) { - return undefined; +export const buildFrameworkGlobalsFromOptions = async (options: Options) => { + const globals: Record = {}; + + const { renderer, builder } = await options.presets.apply('core'); + + const rendererName = pluckNameFromConfigProperty(renderer); + if (rendererName) { + globals.STORYBOOK_RENDERER = rendererName; + } + + const builderName = pluckNameFromConfigProperty(builder); + if (builderName) { + globals.STORYBOOK_BUILDER = builderName; } - const name = typeof builderValue === 'string' ? builderValue : builderValue.name; + const framework = pluckNameFromConfigProperty(await options.presets.apply('framework')); + if (framework) { + globals.STORYBOOK_FRAMEWORK = framework; + } - return name.includes('webpack5') ? 'webpack5' : name; + return globals; }; From ac8a243be1f42942b940052af4e402ccaa37c7a3 Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Thu, 18 Jan 2024 08:36:42 -0500 Subject: [PATCH 11/17] Add tests for framework utils --- .../src/utils/framework.test.ts | 43 +++++++++++++++++++ .../builder-manager/src/utils/framework.ts | 15 +++++-- code/lib/manager-api/src/modules/versions.ts | 17 ++++++-- 3 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 code/builders/builder-manager/src/utils/framework.test.ts diff --git a/code/builders/builder-manager/src/utils/framework.test.ts b/code/builders/builder-manager/src/utils/framework.test.ts new file mode 100644 index 000000000000..60e771875662 --- /dev/null +++ b/code/builders/builder-manager/src/utils/framework.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect } from 'vitest'; + +import { + pluckNameFromConfigProperty, + pluckStorybookPackageFromPath, + pluckThirdPartyPackageFromPath, +} from './framework'; + +describe('UTILITIES: Framework information', () => { + describe('UTILITY: pluckNameFromConfigProperty', () => { + it('should return undefined if the property is undefined', () => { + expect(pluckNameFromConfigProperty(undefined)).toBe(undefined); + }); + + it('should return the name if the property is a string', () => { + expect(pluckNameFromConfigProperty('foo')).toBe('foo'); + }); + + it('should return the name if the property is an object', () => { + expect(pluckNameFromConfigProperty({ name: 'foo' })).toBe('foo'); + }); + }); + + describe('UTILITY: pluckStorybookPackageFromPath', () => { + it('should return the package name if the path is a storybook package', () => { + expect(pluckStorybookPackageFromPath('@storybook/foo')).toBe('@storybook/foo'); + }); + + it('should return undefined if the path is not a storybook package', () => { + expect(pluckStorybookPackageFromPath('foo')).toBe(undefined); + }); + }); + + describe('UTILITY: pluckThirdPartyPackageFromPath', () => { + it('should return the package name if the path is a third party package', () => { + expect(pluckThirdPartyPackageFromPath('foo/node_modules/bar')).toBe('bar'); + }); + + it('should return the given path if the path is not a third party package', () => { + expect(pluckThirdPartyPackageFromPath('foo/bar/baz')).toBe('foo/bar/baz'); + }); + }); +}); diff --git a/code/builders/builder-manager/src/utils/framework.ts b/code/builders/builder-manager/src/utils/framework.ts index 84941fe360ff..f93075684a6d 100644 --- a/code/builders/builder-manager/src/utils/framework.ts +++ b/code/builders/builder-manager/src/utils/framework.ts @@ -1,3 +1,4 @@ +import path from 'path'; import type { Options } from '@storybook/types'; interface PropertyObject { @@ -7,7 +8,7 @@ interface PropertyObject { type Property = string | PropertyObject | undefined; -const pluckNameFromConfigProperty = (property: Property) => { +export const pluckNameFromConfigProperty = (property: Property) => { if (!property) { return undefined; } @@ -15,6 +16,12 @@ const pluckNameFromConfigProperty = (property: Property) => { return typeof property === 'string' ? property : property.name; }; +export const pluckStorybookPackageFromPath = (packagePath: string) => + packagePath.match(/(@storybook\/.*)$/)?.[1]; + +export const pluckThirdPartyPackageFromPath = (packagePath: string) => + packagePath.split(`${path.sep}node_modules${path.sep}`)[1] ?? packagePath; + export const buildFrameworkGlobalsFromOptions = async (options: Options) => { const globals: Record = {}; @@ -22,12 +29,14 @@ export const buildFrameworkGlobalsFromOptions = async (options: Options) => { const rendererName = pluckNameFromConfigProperty(renderer); if (rendererName) { - globals.STORYBOOK_RENDERER = rendererName; + globals.STORYBOOK_RENDERER = + pluckStorybookPackageFromPath(rendererName) ?? pluckThirdPartyPackageFromPath(rendererName); } const builderName = pluckNameFromConfigProperty(builder); if (builderName) { - globals.STORYBOOK_BUILDER = builderName; + globals.STORYBOOK_BUILDER = + pluckStorybookPackageFromPath(builderName) ?? pluckThirdPartyPackageFromPath(builderName); } const framework = pluckNameFromConfigProperty(await options.presets.apply('framework')); diff --git a/code/lib/manager-api/src/modules/versions.ts b/code/lib/manager-api/src/modules/versions.ts index 7564b02919b5..a414c07fe073 100644 --- a/code/lib/manager-api/src/modules/versions.ts +++ b/code/lib/manager-api/src/modules/versions.ts @@ -23,6 +23,14 @@ const getVersionCheckData = memoize(1)((): API_Versions => { } }); +const normalizeRendererName = (renderer: string) => { + if (renderer.includes('vue')) { + return 'vue'; + } + + return renderer; +}; + export interface SubAPI { /** * Returns the current version of the Storybook Manager. @@ -87,9 +95,6 @@ export const init: ModuleFn = ({ store }) => { let url = 'https://storybook.js.org/docs/'; - console.log('current', current); - console.log('latest', latest); - if (versioned && current?.version && latest?.version) { const versionDiff = semver.diff(latest.version, current.version); const isLatestDocs = versionDiff === 'patch' || versionDiff === null; @@ -104,7 +109,11 @@ export const init: ModuleFn = ({ store }) => { } if (renderer && typeof global.STORYBOOK_RENDERER !== 'undefined') { - url += `?renderer=${global.STORYBOOK_RENDERER}`; + const rendererName = (global.STORYBOOK_RENDERER as string).split('/').pop()?.toLowerCase(); + + if (rendererName) { + url += `?renderer=${normalizeRendererName(rendererName)}`; + } } return url; From 4b8dc744961634be5664923de12d28b6c3209605 Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Thu, 18 Jan 2024 09:10:44 -0500 Subject: [PATCH 12/17] Add global vars to typings def --- code/lib/manager-api/src/typings.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/lib/manager-api/src/typings.d.ts b/code/lib/manager-api/src/typings.d.ts index 63edef9413da..afbe02b0417f 100644 --- a/code/lib/manager-api/src/typings.d.ts +++ b/code/lib/manager-api/src/typings.d.ts @@ -8,3 +8,6 @@ declare var REFS: any; declare var VERSIONCHECK: any; declare var LOGLEVEL: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent' | undefined; declare var STORYBOOK_ADDON_STATE: Record; +declare var STORYBOOK_RENDERER: string | undefined; +declare var STORYBOOK_BUILDER: string | undefined; +declare var STORYBOOK_FRAMEWORK: string | undefined; From 0735b1f3d996d37777a287b6acd205f6b6142594 Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Thu, 18 Jan 2024 09:21:08 -0500 Subject: [PATCH 13/17] Remove unused import --- code/addons/interactions/src/components/InteractionsPanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/code/addons/interactions/src/components/InteractionsPanel.tsx b/code/addons/interactions/src/components/InteractionsPanel.tsx index bf7e0b2f3789..a5b58d94b680 100644 --- a/code/addons/interactions/src/components/InteractionsPanel.tsx +++ b/code/addons/interactions/src/components/InteractionsPanel.tsx @@ -8,7 +8,6 @@ import { Subnav } from './Subnav'; import { Interaction } from './Interaction'; import { isTestAssertionError } from '../utils'; import { Empty } from './EmptyState'; -import { DOCUMENTATION_LINK } from '../constants'; export interface Controls { start: (args: any) => void; From 627138bcec326f78ee19157c224d5bb0607820f9 Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Thu, 18 Jan 2024 09:56:46 -0500 Subject: [PATCH 14/17] Fix CI errors --- .../builder-manager/src/utils/framework.test.ts | 13 +++++++++---- code/ui/manager/src/container/Menu.tsx | 10 ---------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/code/builders/builder-manager/src/utils/framework.test.ts b/code/builders/builder-manager/src/utils/framework.test.ts index 60e771875662..36a42193216e 100644 --- a/code/builders/builder-manager/src/utils/framework.test.ts +++ b/code/builders/builder-manager/src/utils/framework.test.ts @@ -1,3 +1,4 @@ +import path from 'node:path'; import { describe, it, expect } from 'vitest'; import { @@ -23,21 +24,25 @@ describe('UTILITIES: Framework information', () => { describe('UTILITY: pluckStorybookPackageFromPath', () => { it('should return the package name if the path is a storybook package', () => { - expect(pluckStorybookPackageFromPath('@storybook/foo')).toBe('@storybook/foo'); + const packagePath = path.join(process.cwd(), 'node_modules', '@storybook/foo'); + expect(pluckStorybookPackageFromPath(packagePath)).toBe('@storybook/foo'); }); it('should return undefined if the path is not a storybook package', () => { - expect(pluckStorybookPackageFromPath('foo')).toBe(undefined); + const packagePath = path.join(process.cwd(), 'foo'); + expect(pluckStorybookPackageFromPath(packagePath)).toBe(undefined); }); }); describe('UTILITY: pluckThirdPartyPackageFromPath', () => { it('should return the package name if the path is a third party package', () => { - expect(pluckThirdPartyPackageFromPath('foo/node_modules/bar')).toBe('bar'); + const packagePath = path.join(process.cwd(), 'node_modules', 'bar'); + expect(pluckThirdPartyPackageFromPath(packagePath)).toBe('bar'); }); it('should return the given path if the path is not a third party package', () => { - expect(pluckThirdPartyPackageFromPath('foo/bar/baz')).toBe('foo/bar/baz'); + const packagePath = path.join(process.cwd(), 'foo', 'bar', 'baz'); + expect(pluckThirdPartyPackageFromPath(packagePath)).toBe(packagePath); }); }); }); diff --git a/code/ui/manager/src/container/Menu.tsx b/code/ui/manager/src/container/Menu.tsx index 21a3a445c83f..77df6aecdd01 100644 --- a/code/ui/manager/src/container/Menu.tsx +++ b/code/ui/manager/src/container/Menu.tsx @@ -48,16 +48,6 @@ export const Shortcut: FC<{ keys: string[] }> = ({ keys }) => ( ); -const sanitizeRendererForDocsUrl = (renderer: string) => { - const normalizedRenderer = renderer.toLowerCase(); - - if (normalizedRenderer.includes('vue')) { - return 'vue'; - } - - return normalizedRenderer; -}; - export const useMenu = ( state: State, api: API, From cca61091f0013a8573729b800447eafc92aba1e5 Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Thu, 18 Jan 2024 10:32:23 -0500 Subject: [PATCH 15/17] Normalize path for npm packages --- code/builders/builder-manager/src/utils/framework.test.ts | 2 +- code/builders/builder-manager/src/utils/framework.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/code/builders/builder-manager/src/utils/framework.test.ts b/code/builders/builder-manager/src/utils/framework.test.ts index 36a42193216e..ecdcb42ead50 100644 --- a/code/builders/builder-manager/src/utils/framework.test.ts +++ b/code/builders/builder-manager/src/utils/framework.test.ts @@ -24,7 +24,7 @@ describe('UTILITIES: Framework information', () => { describe('UTILITY: pluckStorybookPackageFromPath', () => { it('should return the package name if the path is a storybook package', () => { - const packagePath = path.join(process.cwd(), 'node_modules', '@storybook/foo'); + const packagePath = path.join(process.cwd(), 'node_modules', '@storybook', 'foo'); expect(pluckStorybookPackageFromPath(packagePath)).toBe('@storybook/foo'); }); diff --git a/code/builders/builder-manager/src/utils/framework.ts b/code/builders/builder-manager/src/utils/framework.ts index f93075684a6d..165b018ddf8f 100644 --- a/code/builders/builder-manager/src/utils/framework.ts +++ b/code/builders/builder-manager/src/utils/framework.ts @@ -16,11 +16,14 @@ export const pluckNameFromConfigProperty = (property: Property) => { return typeof property === 'string' ? property : property.name; }; +// For replacing Windows backslashes with forward slashes +const normalizePath = (packagePath: string) => packagePath.replaceAll(path.sep, '/'); + export const pluckStorybookPackageFromPath = (packagePath: string) => - packagePath.match(/(@storybook\/.*)$/)?.[1]; + normalizePath(packagePath).match(/(@storybook\/.*)$/)?.[1]; export const pluckThirdPartyPackageFromPath = (packagePath: string) => - packagePath.split(`${path.sep}node_modules${path.sep}`)[1] ?? packagePath; + normalizePath(packagePath).split('node_modules/')[1] ?? packagePath; export const buildFrameworkGlobalsFromOptions = async (options: Options) => { const globals: Record = {}; From c40673eac182086b34e41bf6fdc15089d560e25b Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Thu, 18 Jan 2024 11:41:04 -0500 Subject: [PATCH 16/17] Mock getDocsUrl --- code/ui/manager/src/components/sidebar/Menu.stories.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/ui/manager/src/components/sidebar/Menu.stories.tsx b/code/ui/manager/src/components/sidebar/Menu.stories.tsx index ca89361f179e..98788518db4b 100644 --- a/code/ui/manager/src/components/sidebar/Menu.stories.tsx +++ b/code/ui/manager/src/components/sidebar/Menu.stories.tsx @@ -56,6 +56,7 @@ export const Expanded: Story = { getAddonsShortcuts: () => ({}), versionUpdateAvailable: () => false, isWhatsNewUnread: () => true, + getDocsUrl: () => 'https://storybook.js.org/docs/', }, false, false, @@ -99,6 +100,7 @@ export const ExpandedWithoutWhatsNew: Story = { getAddonsShortcuts: () => ({}), versionUpdateAvailable: () => false, isWhatsNewUnread: () => false, + getDocsUrl: () => 'https://storybook.js.org/docs/', }, false, false, From a6fe1636f9eff274394a27b7b82a9a648cb8f69e Mon Sep 17 00:00:00 2001 From: Shaun Lloyd Date: Fri, 19 Jan 2024 08:14:54 -0500 Subject: [PATCH 17/17] Remove unused import --- code/lib/manager-api/src/tests/versions.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/manager-api/src/tests/versions.test.js b/code/lib/manager-api/src/tests/versions.test.js index 755b5ed0db8a..fbd29142b8d0 100644 --- a/code/lib/manager-api/src/tests/versions.test.js +++ b/code/lib/manager-api/src/tests/versions.test.js @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { global } from '@storybook/global'; -import exp from 'constants'; + import { init as initVersions } from '../modules/versions'; vi.mock('../version', () => ({