Skip to content

Commit

Permalink
Merge pull request #28434 from storybookjs/kasper/docs-filtering
Browse files Browse the repository at this point in the history
Docs: Filter mount stories from `Stories` block, error when referenced in MDX
  • Loading branch information
kasperpeulen authored Jul 3, 2024
2 parents a9682ef + 6b92b98 commit fc64e22
Show file tree
Hide file tree
Showing 13 changed files with 68 additions and 24 deletions.
3 changes: 1 addition & 2 deletions code/core/src/preview-api/modules/preview-web/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import { StoryStore } from '../../store';
import { StoryRender } from './render/StoryRender';
import type { CsfDocsRender } from './render/CsfDocsRender';
import type { MdxDocsRender } from './render/MdxDocsRender';
import { mountDestructured } from './render/mount-utils';

const { fetch } = global;

Expand Down Expand Up @@ -315,7 +314,7 @@ export class Preview<TRenderer extends Renderer> {
.map((r) =>
// We only run the play function, with in a force remount.
// But when mount is destructured, the rendering happens inside of the play function.
r.story && mountDestructured(r.story.playFunction) ? r.remount() : r.rerender()
r.story && r.story.usesMount ? r.remount() : r.rerender()
)
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3664,6 +3664,7 @@ describe('PreviewWeb', () => {
],
"testingLibraryRender": undefined,
"title": "Component One",
"usesMount": false,
},
"component-one--b": {
"argTypes": {
Expand Down Expand Up @@ -3713,6 +3714,7 @@ describe('PreviewWeb', () => {
],
"testingLibraryRender": undefined,
"title": "Component One",
"usesMount": false,
},
"component-one--e": {
"argTypes": {},
Expand Down Expand Up @@ -3740,6 +3742,7 @@ describe('PreviewWeb', () => {
],
"testingLibraryRender": undefined,
"title": "Component One",
"usesMount": false,
},
"component-two--c": {
"argTypes": {
Expand Down Expand Up @@ -3777,6 +3780,7 @@ describe('PreviewWeb', () => {
],
"testingLibraryRender": undefined,
"title": "Component Two",
"usesMount": false,
},
}
`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ describe('StoryRender', () => {

it('does not call mount twice if mount called in play function', async () => {
const story = buildStory({
usesMount: true,
playFunction: async ({ mount }) => {
await mount();
},
Expand Down Expand Up @@ -200,6 +201,7 @@ describe('StoryRender', () => {

it('errors if play function destructures mount but does not call it', async () => {
const story = buildStory({
usesMount: true,
playFunction: async ({ mount }) => {
// forget to call mount
},
Expand Down Expand Up @@ -228,6 +230,7 @@ describe('StoryRender', () => {
});
const story = buildStory({
mount: (context) => () => actualMount(context) as any,
usesMount: true,
playFunction: async ({ mount }) => {
expect(render.phase).toBe('loading');
await mount();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
import type { StoryStore } from '../../store';
import type { Render, RenderType } from './Render';
import { PREPARE_ABORTED } from './Render';
import { mountDestructured } from './mount-utils';
import { MountMustBeDestructuredError, NoStoryMountedError } from '@storybook/core/preview-errors';

import type {
Expand Down Expand Up @@ -191,7 +190,7 @@ export class StoryRender<TRenderer extends Renderer> implements Render<TRenderer

let mounted = false;

const isMountDestructured = playFunction && mountDestructured(playFunction);
const isMountDestructured = story.usesMount;

try {
const context: StoryContext<TRenderer> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { type PreparedStory, type Renderer } from '@storybook/types';

export function mountDestructured<TRenderer extends Renderer>(
playFunction: PreparedStory<TRenderer>['playFunction']
) {
return playFunction && getUsedProps(playFunction).includes('mount');
): boolean {
return playFunction != null && getUsedProps(playFunction).includes('mount');
}
export function getUsedProps(fn: Function) {
const match = fn.toString().match(/[^(]*\(([^)]*)/);
Expand Down
10 changes: 10 additions & 0 deletions code/core/src/preview-api/modules/store/StoryStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ describe('StoryStore', () => {
],
"testingLibraryRender": undefined,
"title": "Component One",
"usesMount": false,
},
}
`);
Expand Down Expand Up @@ -488,6 +489,7 @@ describe('StoryStore', () => {
],
"testingLibraryRender": undefined,
"title": "Component One",
"usesMount": false,
},
"component-one--b": {
"argTypes": {
Expand Down Expand Up @@ -529,6 +531,7 @@ describe('StoryStore', () => {
],
"testingLibraryRender": undefined,
"title": "Component One",
"usesMount": false,
},
"component-two--c": {
"argTypes": {
Expand Down Expand Up @@ -570,6 +573,7 @@ describe('StoryStore', () => {
],
"testingLibraryRender": undefined,
"title": "Component Two",
"usesMount": false,
},
}
`);
Expand Down Expand Up @@ -685,6 +689,7 @@ describe('StoryStore', () => {
"title": "Component One",
"unboundStoryFn": [Function],
"undecoratedStoryFn": [Function],
"usesMount": false,
},
{
"applyBeforeEach": [Function],
Expand Down Expand Up @@ -736,6 +741,7 @@ describe('StoryStore', () => {
"title": "Component One",
"unboundStoryFn": [Function],
"undecoratedStoryFn": [Function],
"usesMount": false,
},
{
"applyBeforeEach": [Function],
Expand Down Expand Up @@ -787,6 +793,7 @@ describe('StoryStore', () => {
"title": "Component Two",
"unboundStoryFn": [Function],
"undecoratedStoryFn": [Function],
"usesMount": false,
},
]
`);
Expand Down Expand Up @@ -849,6 +856,7 @@ describe('StoryStore', () => {
],
"testingLibraryRender": undefined,
"title": "Component One",
"usesMount": false,
},
"component-one--b": {
"argTypes": {
Expand Down Expand Up @@ -890,6 +898,7 @@ describe('StoryStore', () => {
],
"testingLibraryRender": undefined,
"title": "Component One",
"usesMount": false,
},
"component-two--c": {
"argTypes": {
Expand Down Expand Up @@ -931,6 +940,7 @@ describe('StoryStore', () => {
],
"testingLibraryRender": undefined,
"title": "Component Two",
"usesMount": false,
},
},
"v": 2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import { normalizeStory } from './normalizeStory';
import { normalizeComponentAnnotations } from './normalizeComponentAnnotations';
import { getValuesFromArgTypes } from './getValuesFromArgTypes';
import { normalizeProjectAnnotations } from './normalizeProjectAnnotations';
import { mountDestructured } from '../../../modules/preview-web/render/mount-utils';
import { MountMustBeDestructuredError } from '@storybook/core/preview-errors';

let globalProjectAnnotations: ProjectAnnotations<any> = {};
Expand Down Expand Up @@ -343,7 +342,7 @@ async function playStory<TRenderer extends Renderer>(

const playFunction = story.playFunction;

const isMountDestructured = playFunction && mountDestructured(playFunction);
const isMountDestructured = story.usesMount;

if (!isMountDestructured) {
await context.mount();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,7 @@ describe('prepareMeta', () => {
mount,
renderToCanvas,
testingLibraryRender,
usesMount,
...preparedStory
} = prepareStory({ id, name, moduleExport }, meta, { render });

Expand Down
18 changes: 5 additions & 13 deletions code/core/src/preview-api/modules/store/csf/prepareStory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type {
NormalizedStoryAnnotations,
} from '@storybook/core/types';
import { mountDestructured } from '../../preview-web/render/mount-utils';
import { NoRenderFunctionError } from '@storybook/core/preview-errors';

// Combine all the metadata about a story (both direct and inherited from the component/global scope)
// into a "render-able" story function, with all decorators applied, parameters passed as context etc
Expand Down Expand Up @@ -107,19 +108,10 @@ export function prepareStory<TRenderer extends Renderer>(

const playFunction = storyAnnotations?.play ?? componentAnnotations?.play;

const mountUsed = mountDestructured(playFunction);
const usesMount = mountDestructured(playFunction);

if (!render && !mountUsed) {
// TODO Make this a named error
throw new Error(`No render function available for storyId '${id}'`);
}

let { tags } = partialAnnotations;

if (mountUsed) {
// Don't show stories where mount is used in docs.
// As the play function is not running in docs, and when mount is used, the mounting is happening in play itself.
tags = tags.filter((tag) => tag !== 'autodocs');
if (!render && !usesMount) {
throw new NoRenderFunctionError({ id });
}

const defaultMount = (context: StoryContext) => {
Expand All @@ -139,7 +131,6 @@ export function prepareStory<TRenderer extends Renderer>(

return {
...partialAnnotations,
tags,
moduleExport,
id,
name,
Expand All @@ -154,6 +145,7 @@ export function prepareStory<TRenderer extends Renderer>(
mount,
testingLibraryRender,
renderToCanvas: projectAnnotations.renderToCanvas,
usesMount,
};
}
export function prepareMeta<TRenderer extends Renderer>(
Expand Down
19 changes: 18 additions & 1 deletion code/core/src/preview-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { StorybookError } from './storybook-error';
* to prevent manager and preview errors from having the same category and error code.
*/
export enum Category {
BLOCKS = 'BLOCKS',
DOCS_TOOLS = 'DOCS-TOOLS',
PREVIEW_CLIENT_LOGGER = 'PREVIEW_CLIENT-LOGGER',
PREVIEW_CHANNELS = 'PREVIEW_CHANNELS',
Expand Down Expand Up @@ -295,11 +296,27 @@ export class TestingLibraryMustBeConfiguredError extends StorybookError {
}
}

export class NoStoryMountedError extends StorybookError {
export class NoRenderFunctionError extends StorybookError {
readonly category = Category.PREVIEW_API;

readonly code = 14;

constructor(public data: { id: string }) {
super();
}

template() {
return dedent`
No render function available for storyId '${this.data.id}'
`;
}
}

export class NoStoryMountedError extends StorybookError {
readonly category = Category.PREVIEW_API;

readonly code = 15;

template() {
return dedent`
No story is mounted in your story.
Expand Down
1 change: 1 addition & 0 deletions code/core/src/types/modules/story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export type PreparedStory<TRenderer extends Renderer = Renderer> =
mount: (context: StoryContext<TRenderer>) => () => Promise<Canvas>;
testingLibraryRender?: (...args: never[]) => unknown;
renderToCanvas?: ProjectAnnotations<TRenderer>['renderToCanvas'];
usesMount: boolean;
};

export type PreparedMeta<TRenderer extends Renderer = Renderer> = Omit<
Expand Down
4 changes: 3 additions & 1 deletion code/lib/blocks/src/blocks/Stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ export const Stories: FC<StoriesProps> = ({ title = 'Stories', includePrimary =
// the new behavior.
const hasAutodocsTaggedStory = stories.some((story) => story.tags?.includes('autodocs'));
if (hasAutodocsTaggedStory) {
stories = stories.filter((story) => story.tags?.includes('autodocs'));
// Don't show stories where mount is used in docs.
// As the play function is not running in docs, and when mount is used, the mounting is happening in play itself.
stories = stories.filter((story) => story.tags?.includes('autodocs') && !story.usesMount);
}

if (!includePrimary) stories = stories.slice(1);
Expand Down
19 changes: 18 additions & 1 deletion code/lib/blocks/src/components/Story.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* eslint-disable react/destructuring-assignment */
import { global } from '@storybook/global';
import type { FunctionComponent } from 'react';
import React, { useRef, useEffect, useState } from 'react';
import type { DocsContextProps, PreparedStory } from 'storybook/internal/types';
import { Loader, getStoryHref, ErrorFormatter } from 'storybook/internal/components';
import { IFrame } from './IFrame';
import { ZoomContext } from './ZoomContext';
import { styled } from 'storybook/internal/theming';

const { PREVIEW_URL } = global;
const BASE_URL = PREVIEW_URL || 'iframe.html';
Expand Down Expand Up @@ -113,8 +115,23 @@ const IFrameStory: FunctionComponent<IFrameStoryProps> = ({ story, height = '500
* A story element, either rendered inline or in an iframe,
* with configurable height.
*/

const ErrorMessage = styled.strong(({ theme }) => ({
color: theme.color.orange,
}));

const Story: FunctionComponent<StoryProps> = (props) => {
const { inline } = props;
const { inline, story } = props;

if (inline && !props.autoplay && story.usesMount) {
return (
<ErrorMessage>
This story mounts inside of play. Set{' '}
<a href="https://storybook.js.org/docs/api/doc-blocks/doc-block-story#autoplay">autoplay</a>{' '}
to true to view this story.
</ErrorMessage>
);
}

return (
<div id={storyBlockIdFromId(props)} className="sb-story sb-unstyled" data-story-block="true">
Expand Down

0 comments on commit fc64e22

Please sign in to comment.