Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs: Show primary story description and headline in autodocs #20604

Merged
merged 16 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions code/e2e-tests/addon-docs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,12 @@ test.describe('addon-docs', () => {
await sbPage.navigateToStory('addons/docs/docspage/basic', 'docs');

// The `<Primary>` block should render the "Basic" story, and the `<Stories/>` block should
// render the "Another" story
// render both the "Basic" and "Another" story
const root = sbPage.previewRoot();
const stories = root.locator('.sbdocs-h3');

await expect(await stories.count()).toBe(1);
await expect(stories.first()).toHaveText('Another');
await expect(await stories.count()).toBe(2);
await expect(stories.first()).toHaveText('Basic');
await expect(stories.last()).toHaveText('Another');
});
});
2 changes: 1 addition & 1 deletion code/e2e-tests/preview-web.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ test.describe('preview-web', () => {

await expect(sbPage.page.locator('.sidebar-container')).toBeVisible();

await sbPage.previewRoot().getByRole('button').getByText('Submit').press('s');
await sbPage.previewRoot().getByRole('button').getByText('Submit').first().press('s');
await expect(sbPage.page.locator('.sidebar-container')).not.toBeVisible();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export abstract class AbstractRenderer {
parameters: Parameters;
targetDOMNode: HTMLElement;
}) {
const targetSelector = `${this.generateTargetSelectorFromStoryId()}`;
const targetSelector = this.generateTargetSelectorFromStoryId(targetDOMNode.id);

const newStoryProps$ = new BehaviorSubject<ICollection>(storyFnAngular.props);

Expand Down Expand Up @@ -142,12 +142,10 @@ export abstract class AbstractRenderer {
* @protected
* @memberof AbstractRenderer
*/
protected generateTargetSelectorFromStoryId() {
protected generateTargetSelectorFromStoryId(id: string) {
const invalidHtmlTag = /[^A-Za-z0-9-]/g;
const storyIdIsInvalidHtmlTagName = invalidHtmlTag.test(this.storyId);
return storyIdIsInvalidHtmlTagName
? `sb-${this.storyId.replace(invalidHtmlTag, '')}-component`
: this.storyId;
const storyIdIsInvalidHtmlTagName = invalidHtmlTag.test(id);
return storyIdIsInvalidHtmlTagName ? `sb-${id.replace(invalidHtmlTag, '')}-component` : id;
}

protected initAngularRootElement(targetDOMNode: HTMLElement, targetSelector: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class RendererFactory {
storyId: string,
targetDOMNode: HTMLElement
): Promise<AbstractRenderer | null> {
const targetId = targetDOMNode.id;
// do nothing if the target node is null
// fix a problem when the docs asks 2 times the same component at the same time
// the 1st targetDOMNode of the 1st requested rendering becomes null 🤷‍♂️
Expand All @@ -27,12 +28,12 @@ export class RendererFactory {
this.rendererMap.clear();
}

if (!this.rendererMap.has(storyId)) {
this.rendererMap.set(storyId, this.buildRenderer(storyId, renderType));
if (!this.rendererMap.has(targetId)) {
this.rendererMap.set(targetId, this.buildRenderer(storyId, renderType));
}

this.lastRenderType = renderType;
return this.rendererMap.get(storyId);
return this.rendererMap.get(targetId);
}

private buildRenderer(storyId: string, renderType: RenderType) {
Expand Down
6 changes: 5 additions & 1 deletion code/lib/preview-api/src/modules/preview-web/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,11 @@ export class Preview<TFramework extends Renderer> {
async onUpdateArgs({ storyId, updatedArgs }: { storyId: StoryId; updatedArgs: Args }) {
this.storyStore.args.update(storyId, updatedArgs);

await Promise.all(this.storyRenders.filter((r) => r.id === storyId).map((r) => r.rerender()));
await Promise.all(
this.storyRenders
.filter((r) => r.id === storyId && !r.renderOptions.forceInitialArgs)
.map((r) => r.rerender())
);

this.channel.emit(STORY_ARGS_UPDATED, {
storyId,
Expand Down
33 changes: 33 additions & 0 deletions code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,39 @@ describe('PreviewWeb', () => {
'story-element'
);
});

it('does not re-render the story when forceInitialArgs=true', async () => {
document.location.search = '?id=component-one--docs&viewMode=docs';

const preview = await createAndRenderPreview();
await waitForRender();

mockChannel.emit.mockClear();
const story = await preview.storyStore.loadStory({ storyId: 'component-one--a' });
preview.renderStoryToElement(story, 'story-element' as any, { forceInitialArgs: true });
await waitForRender();

expect(projectAnnotations.renderToCanvas).toHaveBeenCalledWith(
expect.objectContaining({
storyContext: expect.objectContaining({
args: { foo: 'a' },
}),
}),
'story-element'
);

docsRenderer.render.mockClear();
mockChannel.emit.mockClear();
emitter.emit(UPDATE_STORY_ARGS, {
storyId: 'component-one--a',
updatedArgs: { new: 'arg' },
});
await waitForEvents([STORY_ARGS_UPDATED]);

// We don't re-render the story
await expect(waitForRender).rejects.toThrow();
expect(projectAnnotations.renderToCanvas).toHaveBeenCalledTimes(1);
});
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,47 @@ describe('StoryRender', () => {
await render.renderToElement({} as any);
expect(story.playFunction).not.toHaveBeenCalled();
});

it('passes the initialArgs to loaders and render function if forceInitialArgs is true', async () => {
const story = {
id: 'id',
title: 'title',
name: 'name',
tags: [],
initialArgs: { a: 'b' },
applyLoaders: jest.fn(),
unboundStoryFn: jest.fn(),
playFunction: jest.fn(),
};

const renderToScreen = jest.fn();

const render = new StoryRender(
new Channel(),
{ getStoryContext: () => ({ args: { a: 'c ' } }) } as any,
renderToScreen as any,
{} as any,
entry.id,
'story',
{ forceInitialArgs: true },
story as any
);

await render.renderToElement({} as any);

expect(story.applyLoaders).toHaveBeenCalledWith(
expect.objectContaining({
args: { a: 'b' },
})
);

expect(renderToScreen).toHaveBeenCalledWith(
expect.objectContaining({
storyContext: expect.objectContaining({
args: { a: 'b' },
}),
}),
expect.any(Object)
);
});
});
24 changes: 19 additions & 5 deletions code/lib/preview-api/src/modules/preview-web/render/StoryRender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export class StoryRender<TRenderer extends Renderer> implements Render<TRenderer
private callbacks: RenderContextCallbacks<TRenderer>,
public id: StoryId,
public viewMode: ViewMode,
public renderOptions: StoryRenderOptions = { autoplay: true },
public renderOptions: StoryRenderOptions = { autoplay: true, forceInitialArgs: false },
story?: PreparedStory<TRenderer>
) {
this.abortController = new AbortController();
Expand Down Expand Up @@ -154,8 +154,17 @@ export class StoryRender<TRenderer extends Renderer> implements Render<TRenderer
if (!this.story) throw new Error('cannot render when not prepared');
if (!canvasElement) throw new Error('cannot render when canvasElement is unset');

const { id, componentId, title, name, tags, applyLoaders, unboundStoryFn, playFunction } =
this.story;
const {
id,
componentId,
title,
name,
tags,
applyLoaders,
unboundStoryFn,
playFunction,
initialArgs,
} = this.story;

if (forceRemount && !initial) {
// NOTE: we don't check the cancel actually worked here, so the previous
Expand All @@ -170,10 +179,15 @@ export class StoryRender<TRenderer extends Renderer> implements Render<TRenderer
const abortSignal = (this.abortController as AbortController).signal;

try {
const getCurrentContext = () => ({
...this.storyContext(),
...(this.renderOptions.forceInitialArgs && { args: initialArgs }),
});

let loadedContext: Awaited<ReturnType<typeof applyLoaders>>;
await this.runPhase(abortSignal, 'loading', async () => {
loadedContext = await applyLoaders({
...this.storyContext(),
...getCurrentContext(),
viewMode: this.viewMode,
} as StoryContextForLoaders<TRenderer>);
});
Expand All @@ -185,7 +199,7 @@ export class StoryRender<TRenderer extends Renderer> implements Render<TRenderer
...loadedContext!,
// By this stage, it is possible that new args/globals have been received for this story
// and we need to ensure we render it with the new values
...this.storyContext(),
...getCurrentContext(),
abortSignal,
// We should consider parameterizing the story types with TRenderer['canvasElement'] in the future
canvasElement: canvasElement as any,
Expand Down
1 change: 1 addition & 0 deletions code/lib/types/src/modules/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {

export type StoryRenderOptions = {
autoplay?: boolean;
forceInitialArgs?: boolean;
};

/**
Expand Down
9 changes: 8 additions & 1 deletion code/ui/blocks/src/blocks/DocsStory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const DocsStory: FC<DocsStoryProps> = ({
expanded = true,
withToolbar = false,
parameters = {},
__forceInitialArgs = false,
__primary = false,
}) => {
let description;
const { docs } = parameters;
Expand All @@ -27,7 +29,12 @@ export const DocsStory: FC<DocsStoryProps> = ({
{subheading && <Subheading>{subheading}</Subheading>}
{description && <Description markdown={description} />}
<Canvas withToolbar={withToolbar}>
<Story id={id} parameters={parameters} />
<Story
id={id}
parameters={parameters}
__forceInitialArgs={__forceInitialArgs}
__primary={__primary}
/>
</Canvas>
</Anchor>
);
Expand Down
2 changes: 1 addition & 1 deletion code/ui/blocks/src/blocks/Primary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ export const Primary: FC<PrimaryProps> = ({ name }) => {
const docsContext = useContext(DocsContext);
const storyId = name && docsContext.storyIdByName(name);
const story = docsContext.storyById(storyId);
return story ? <DocsStory {...story} expanded={false} withToolbar /> : null;
return story ? <DocsStory {...story} expanded={false} withToolbar __primary /> : null;
};
6 changes: 4 additions & 2 deletions code/ui/blocks/src/blocks/Stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface StoriesProps {
includePrimary?: boolean;
}

export const Stories: FC<StoriesProps> = ({ title, includePrimary = false }) => {
export const Stories: FC<StoriesProps> = ({ title, includePrimary = true }) => {
const { componentStories } = useContext(DocsContext);

let stories: DocsStoryProps[] = componentStories();
Expand All @@ -23,7 +23,9 @@ export const Stories: FC<StoriesProps> = ({ title, includePrimary = false }) =>
return (
<>
<Heading>{title}</Heading>
{stories.map((story) => story && <DocsStory key={story.id} {...story} expanded />)}
{stories.map(
(story) => story && <DocsStory key={story.id} {...story} expanded __forceInitialArgs />
)}
</>
);
};
Expand Down
16 changes: 13 additions & 3 deletions code/ui/blocks/src/blocks/Story.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';

import { Story as StoryComponent } from './Story';
import { Story as StoryBlock } from './Story';
import * as ButtonStories from '../examples/Button.stories';
import * as StoryComponentStories from '../components/Story.stories';

const meta: Meta<typeof StoryComponent> = {
component: StoryComponent,
const meta: Meta<typeof StoryBlock> = {
component: StoryBlock,
parameters: {
relativeCsfPaths: ['../examples/Button.stories', '../blocks/Story.stories'],
},
Expand Down Expand Up @@ -199,3 +200,12 @@ export const WithInteractionsAutoplayInStory: Story = {
chromatic: { delay: 500 },
},
};

export const ForceInitialArgs: Story = {
...StoryComponentStories.ForceInitialArgs,
args: {
of: ButtonStories.Primary,
storyExport: ButtonStories.Primary,
__forceInitialArgs: true,
} as any,
};
24 changes: 16 additions & 8 deletions code/ui/blocks/src/blocks/Story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import type { DocsContextProps } from './DocsContext';
import { DocsContext } from './DocsContext';
import { useStory } from './useStory';

export const storyBlockIdFromId = (storyId: string) => `story--${storyId}`;

type PureStoryProps = ComponentProps<typeof PureStory>;

/**
Expand Down Expand Up @@ -72,6 +70,14 @@ type StoryParameters = {
* Whether to run the story's play function
*/
autoplay?: boolean;
/**
* Internal prop to control if a story re-renders on args updates
*/
__forceInitialArgs?: boolean;
/**
* Internal prop if this story is the primary story
*/
__primary?: boolean;
};

export type StoryProps = (StoryDefProps | StoryRefProps) & StoryParameters;
Expand Down Expand Up @@ -136,6 +142,10 @@ export const getStoryProps = <TFramework extends Renderer>(
inline: true,
height,
autoplay,
// eslint-disable-next-line no-underscore-dangle
forceInitialArgs: !!props.__forceInitialArgs,
// eslint-disable-next-line no-underscore-dangle
primary: !!props.__primary,
renderStoryToElement: context.renderStoryToElement,
};
}
Expand All @@ -147,10 +157,12 @@ export const getStoryProps = <TFramework extends Renderer>(
story,
inline: false,
height,
// eslint-disable-next-line no-underscore-dangle
primary: !!props.__primary,
};
};

const Story: FC<StoryProps> = (props) => {
const Story: FC<StoryProps> = (props = { __forceInitialArgs: false, __primary: false }) => {
const context = useContext(DocsContext);
const storyId = getStoryId(props, context);
const story = useStory(storyId, context);
Expand All @@ -164,11 +176,7 @@ const Story: FC<StoryProps> = (props) => {
return null;
}

return (
<div id={storyBlockIdFromId(story.id)} className="sb-story">
<PureStory {...storyProps} />
</div>
);
return <PureStory {...storyProps} />;
};

export { Story };
2 changes: 2 additions & 0 deletions code/ui/blocks/src/blocks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ export interface StoryData {
export type DocsStoryProps = StoryData & {
expanded?: boolean;
withToolbar?: boolean;
__forceInitialArgs?: boolean;
__primary?: boolean;
};
Loading