diff --git a/code/core/src/manager-api/modules/versions.ts b/code/core/src/manager-api/modules/versions.ts index 89a84be2072a..c3b2f38ddcdc 100644 --- a/code/core/src/manager-api/modules/versions.ts +++ b/code/core/src/manager-api/modules/versions.ts @@ -49,17 +49,19 @@ export interface SubAPI { * Returns the URL of the Storybook documentation for the current version. * * @param options - The options for the documentation URL. - * @param options.asset - Links to the docs-assets directory instead of docs. + * @param options.asset - Like subpath, but links to the docs-assets directory. * @param options.subpath - The subpath of the documentation URL. * @param options.versioned - Whether to include the versioned path. * @param options.renderer - Whether to include the renderer path. + * @param options.ref - Tracking reference for the docs site. E.g. 'ui', 'error', 'upgrade', etc. * @returns {string} The URL of the Storybook Manager documentation. */ getDocsUrl: (options: { - asset?: boolean; + asset?: string; subpath?: string; versioned?: boolean; renderer?: boolean; + ref?: string; }) => string; /** * Checks if an update is available for the Storybook Manager. @@ -99,7 +101,7 @@ export const init: ModuleFn = ({ store }) => { return latest as API_Version; }, // TODO: Move this to it's own "info" module later - getDocsUrl: ({ asset, subpath, versioned, renderer }) => { + getDocsUrl: ({ asset, subpath = asset, versioned, renderer, ref = 'ui' }) => { const { versions: { latest, current }, } = store.getState(); @@ -135,6 +137,10 @@ export const init: ModuleFn = ({ store }) => { } } + if (ref) { + url += `${url.includes('?') ? '&' : '?'}ref=${ref}`; + } + if (hash) { url += `#${hash}`; } diff --git a/code/core/src/manager-api/tests/versions.test.js b/code/core/src/manager-api/tests/versions.test.js index 9ccf0de55fdd..5f3895bd0ce0 100644 --- a/code/core/src/manager-api/tests/versions.test.js +++ b/code/core/src/manager-api/tests/versions.test.js @@ -48,6 +48,38 @@ function createMockStore() { vi.mock('storybook/internal/client-logger'); +const latest = { + current: { version: '7.6.1' }, + latest: { version: '7.6.1' }, +}; +const patchDiff = { + current: { version: '7.6.1' }, + latest: { version: '7.6.10' }, +}; +const minorDiff = { + current: { version: '7.2.5' }, + latest: { version: '7.6.10' }, +}; +const majorDiff = { + current: { version: '6.2.1' }, + latest: { version: '7.6.10' }, +}; +const newerPrerelease = { + current: { version: '8.0.0-beta' }, + latest: { version: '7.6.10' }, +}; +const olderPrerelease = { + current: { version: '5.2.1-prerelease.0' }, + latest: { version: '6.2.1' }, +}; +const prereleaseAvailable = { + current: { version: '5.2.1' }, + latest: { version: '6.2.1-prerelease.0' }, +}; + +const setVersions = (store, state, versions) => + store.setState({ ...state, versions: { ...state.versions, ...versions } }); + describe('versions API', () => { it('sets initial state with current version', async () => { const store = createMockStore(); @@ -72,9 +104,8 @@ describe('versions API', () => { it('sets versions in the init function', async () => { const store = createMockStore(); - const { state: initialState, init } = initVersions({ - store, - }); + const { state: initialState, init } = initVersions({ store }); + store.setState(initialState); store.setState.mockReset(); @@ -91,13 +122,8 @@ describe('versions API', () => { it('getCurrentVersion works', async () => { const store = createMockStore(); - const { - init, - api, - state: initialState, - } = initVersions({ - store, - }); + const { init, api, state: initialState } = initVersions({ store }); + store.setState(initialState); await init(); @@ -109,13 +135,8 @@ describe('versions API', () => { it('getLatestVersion works', async () => { const store = createMockStore(); - const { - init, - api, - state: initialState, - } = initVersions({ - store, - }); + const { init, api, state: initialState } = initVersions({ store }); + store.setState(initialState); await init(); @@ -132,174 +153,101 @@ describe('versions API', () => { it('returns the latest url when current version is latest', async () => { const store = createMockStore(); - const { - init, - api, - state: initialState, - } = initVersions({ - store, - }); + 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' }, - }, - }); + setVersions(store, initialState, latest); - expect(api.getDocsUrl({ versioned: true })).toEqual('https://storybook.js.org/docs/'); + expect(api.getDocsUrl({ versioned: true })).toEqual('https://storybook.js.org/docs/?ref=ui'); }); it('returns the latest url when version has patch diff with latest', async () => { const store = createMockStore(); - const { - init, - api, - state: initialState, - } = initVersions({ - store, - }); + 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' }, - }, - }); + setVersions(store, initialState, patchDiff); - expect(api.getDocsUrl({ versioned: true })).toEqual('https://storybook.js.org/docs/'); + expect(api.getDocsUrl({ versioned: true })).toEqual('https://storybook.js.org/docs/?ref=ui'); }); it('returns the versioned url when current has different docs to latest', async () => { const store = createMockStore(); - const { - init, - api, - state: initialState, - } = initVersions({ - store, - }); + 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' }, - }, - }); + setVersions(store, initialState, minorDiff); - expect(api.getDocsUrl({ versioned: true })).toEqual('https://storybook.js.org/docs/7.2/'); + expect(api.getDocsUrl({ versioned: true })).toEqual( + 'https://storybook.js.org/docs/7.2/?ref=ui' + ); }); it('returns the versioned url when current is a prerelease', async () => { const store = createMockStore(); - const { - init, - api, - state: initialState, - } = initVersions({ - store, - }); + 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' }, - }, - }); + setVersions(store, initialState, newerPrerelease); - expect(api.getDocsUrl({ versioned: true })).toEqual('https://storybook.js.org/docs/8.0/'); + expect(api.getDocsUrl({ versioned: true })).toEqual( + 'https://storybook.js.org/docs/8.0/?ref=ui' + ); }); 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' }, - }, - }); + const { init, api, state: initialState } = initVersions({ store }); + + setVersions(store, initialState, latest); await init(); global.STORYBOOK_RENDERER = 'vue'; expect(api.getDocsUrl({ renderer: true })).toEqual( - 'https://storybook.js.org/docs/?renderer=vue' + 'https://storybook.js.org/docs/?renderer=vue&ref=ui' ); }); - it('returns a url with assets path when "asset" is true', async () => { + it('returns a url with a custom ref query param when provided', async () => { const store = createMockStore(); - const { - init, - api, - state: initialState, - } = initVersions({ - store, - }); + 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' }, - }, - }); + setVersions(store, initialState, latest); + + expect(api.getDocsUrl({ ref: 'custom' })).toEqual( + 'https://storybook.js.org/docs/?ref=custom' + ); + }); + + it('returns a url without ref query param when empty', async () => { + const store = createMockStore(); + const { init, api, state: initialState } = initVersions({ store }); + + await init(); - expect(api.getDocsUrl({ asset: true })).toEqual('https://storybook.js.org/docs-assets/7.6/'); + setVersions(store, initialState, latest); + + expect(api.getDocsUrl({ ref: '' })).toEqual('https://storybook.js.org/docs/'); }); - it('returns a url with subpath when provided', async () => { + it('returns a versioned url with assets path when provided', async () => { const store = createMockStore(); - const { - init, - api, - state: initialState, - } = initVersions({ - store, - }); + 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' }, - }, - }); + setVersions(store, initialState, latest); - expect(api.getDocsUrl({ asset: true, subpath: 'api/doc-block-controls.png' })).toEqual( - 'https://storybook.js.org/docs-assets/7.2/api/doc-block-controls.png' + expect(api.getDocsUrl({ asset: 'api/doc-block-controls.png' })).toEqual( + 'https://storybook.js.org/docs-assets/7.6/api/doc-block-controls.png?ref=ui' ); }); }); @@ -307,21 +255,9 @@ describe('versions API', () => { describe('versionUpdateAvailable', () => { it('matching version', 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' }, - }, - }); + const { init, api, state: initialState } = initVersions({ store }); + + setVersions(store, initialState, latest); await init(); @@ -330,21 +266,9 @@ describe('versions API', () => { it('new patch version', 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.2' }, - }, - }); + const { init, api, state: initialState } = initVersions({ store }); + + setVersions(store, initialState, patchDiff); await init(); @@ -353,72 +277,33 @@ describe('versions API', () => { it('new minor version', async () => { const store = createMockStore(); - const { - init, - api, - state: initialState, - } = initVersions({ - store, - }); + const { init, api, state: initialState } = initVersions({ store }); await init(); - store.setState({ - ...initialState, - versions: { - ...initialState.versions, - current: { version: '5.2.1' }, - latest: { version: '5.3.1' }, - }, - }); + setVersions(store, initialState, minorDiff); expect(api.versionUpdateAvailable()).toEqual(true); }); it('new major version', async () => { const store = createMockStore(); - const { - init, - api, - state: initialState, - } = initVersions({ - store, - }); + const { init, api, state: initialState } = initVersions({ store }); await init(); - store.setState({ - ...initialState, - versions: { - ...initialState.versions, - current: { version: '5.2.1' }, - latest: { version: '6.2.1' }, - }, - }); + setVersions(store, initialState, majorDiff); expect(api.versionUpdateAvailable()).toEqual(true); }); it('new prerelease version', async () => { const store = createMockStore(); - const { - init, - api, - state: initialState, - } = initVersions({ - store, - }); + const { init, api, state: initialState } = initVersions({ store }); await init(); - store.setState({ - ...initialState, - versions: { - ...initialState.versions, - current: { version: '5.2.1' }, - latest: { version: '6.2.1-prerelease.0' }, - }, - }); + setVersions(store, initialState, prereleaseAvailable); expect(api.versionUpdateAvailable()).toEqual(false); }); @@ -429,38 +314,18 @@ describe('versions API', () => { await init(); - store.setState({ - ...initialState, - versions: { - ...initialState.versions, - current: { version: '5.2.1-prerelease.0' }, - latest: { version: '6.2.1' }, - }, - }); + setVersions(store, initialState, olderPrerelease); expect(api.versionUpdateAvailable()).toEqual(true); }); it('from newer prerelease version', async () => { const store = createMockStore(); - const { - init, - api, - state: initialState, - } = initVersions({ - store, - }); + const { init, api, state: initialState } = initVersions({ store }); await init(); - store.setState({ - ...initialState, - versions: { - ...initialState.versions, - current: { version: '5.2.1-prerelease.0' }, - latest: { version: '3.2.1' }, - }, - }); + setVersions(store, initialState, newerPrerelease); expect(api.versionUpdateAvailable()).toEqual(false); }); diff --git a/code/core/src/shared/checklist-store/checklistData.tsx b/code/core/src/shared/checklist-store/checklistData.tsx index 20c5d23bdf22..eac4cadd3821 100644 --- a/code/core/src/shared/checklist-store/checklistData.tsx +++ b/code/core/src/shared/checklist-store/checklistData.tsx @@ -196,14 +196,22 @@ export const checklistData = {

Rendering your components can often require{' '} setting up surrounding context in decorators {' '} or{' '} applying global styles @@ -242,7 +250,11 @@ export const Primary: Story = {

@@ -264,7 +276,10 @@ export const Primary: Story = { Autocomplete, or even full pages.

Components in the sidebar

@@ -272,6 +287,7 @@ export const Primary: Story = { href={api.getDocsUrl({ subpath: 'get-started/whats-a-story#create-a-new-story', renderer: true, + ref: 'guide', })} target="_blank" withArrow @@ -302,8 +318,8 @@ export const Primary: Story = {

Stories in the sidebar @@ -312,6 +328,7 @@ export const Primary: Story = { href={api.getDocsUrl({ subpath: 'essentials/controls#creating-and-editing-stories-from-controls', renderer: true, + ref: 'guide', })} target="_blank" withArrow @@ -363,14 +380,21 @@ export const Primary: Story = { component handles various inputs.

Screenshot of Controls block Take it further

Read the{' '} Controls documentation @@ -398,14 +422,21 @@ export const Primary: Story = { built-in support for previewing stories in various device sizes.

Screenshot of Viewports menu Take it further

Read the{' '} Viewports documentation @@ -449,7 +480,10 @@ export default {

Which would look like:

Grouped components in the sidebar Take it further @@ -459,6 +493,7 @@ export default { href={api.getDocsUrl({ subpath: 'writing-stories/naming-components-and-hierarchy', renderer: true, + ref: 'guide', })} target="_blank" > @@ -503,8 +538,8 @@ export default {

Storybook app with story status indicators, testing widget, and addon panel annotated @@ -513,6 +548,7 @@ export default { href={api.getDocsUrl({ subpath: 'writing-tests/integrations/vitest-addon', renderer: true, + ref: 'guide', })} target="_blank" withArrow @@ -576,8 +612,8 @@ export default {

Test widget showing test failures @@ -588,8 +624,8 @@ export default {

Screenshot of story sidebar item with open menu @@ -600,6 +636,7 @@ export default { href={api.getDocsUrl({ subpath: 'writing-tests#component-tests', renderer: true, + ref: 'guide', })} target="_blank" > @@ -677,8 +714,8 @@ export const Disabled: Story = {

Storybook with a LoginForm component and passing interactions in the Interactions panel @@ -689,6 +726,7 @@ export const Disabled: Story = { href={api.getDocsUrl({ subpath: 'writing-tests/interaction-testing', renderer: true, + ref: 'guide', })} target="_blank" > @@ -735,6 +773,7 @@ export const Disabled: Story = { href={api.getDocsUrl({ subpath: 'writing-tests/accessibility-testing', renderer: true, + ref: 'guide', })} target="_blank" withArrow @@ -763,8 +802,8 @@ export const Disabled: Story = {

Testing widget with accessibility activated @@ -774,8 +813,8 @@ export const Disabled: Story = {

Storybook app with accessibility panel open, showing violations and an interactive popover on the violating elements in the preview @@ -786,6 +825,7 @@ export const Disabled: Story = { href={api.getDocsUrl({ subpath: 'writing-tests/accessibility-testing', renderer: true, + ref: 'guide', })} target="_blank" > @@ -829,6 +869,7 @@ export const Disabled: Story = { href={api.getDocsUrl({ subpath: 'writing-tests/visual-testing', renderer: true, + ref: 'guide', })} target="_blank" withArrow @@ -851,8 +892,8 @@ export const Disabled: Story = {

Expand the test widget and click the Run visual tests button.

Expanded testing widget, showing the Visual tests section @@ -863,8 +904,8 @@ export const Disabled: Story = {

Visual tests addon panel showing a diff from the baseline @@ -875,6 +916,7 @@ export const Disabled: Story = { href={api.getDocsUrl({ subpath: 'writing-tests/visual-testing', renderer: true, + ref: 'guide', })} target="_blank" > @@ -914,8 +956,8 @@ export const Disabled: Story = {

Test widget with coverage summary @@ -926,6 +968,7 @@ export const Disabled: Story = { href={api.getDocsUrl({ subpath: 'writing-tests/test-coverage', renderer: true, + ref: 'guide', })} target="_blank" > @@ -956,8 +999,8 @@ export const Disabled: Story = {

GitHub pull request status checks, with a failing "UI Tests / test" check @@ -968,6 +1011,7 @@ export const Disabled: Story = { href={api.getDocsUrl({ subpath: 'writing-tests/in-ci', renderer: true, + ref: 'guide', })} target="_blank" > @@ -1014,6 +1058,7 @@ export const Disabled: Story = { href={api.getDocsUrl({ subpath: 'writing-docs', renderer: true, + ref: 'guide', })} target="_blank" withArrow @@ -1055,8 +1100,8 @@ export default {

Storybook autodocs page, showing a title, description, primary story, controls table, and additional stories @@ -1067,6 +1112,7 @@ export default { href={api.getDocsUrl({ subpath: 'writing-docs/autodocs', renderer: true, + ref: 'guide', })} target="_blank" > @@ -1103,7 +1149,11 @@ export default { For a start, create an introduction.mdx file and (using markdown and Storybook's{' '} doc blocks @@ -1138,6 +1188,7 @@ npm install @my/awesome-project href={api.getDocsUrl({ subpath: 'writing-docs/mdx', renderer: true, + ref: 'guide', })} target="_blank" > @@ -1179,8 +1230,8 @@ npm install @my/awesome-project

PR check for publish action @@ -1191,6 +1242,7 @@ npm install @my/awesome-project href={api.getDocsUrl({ subpath: 'sharing/publish-storybook', renderer: true, + ref: 'guide', })} target="_blank" >