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

Portable Stories: Fix injected root element changing layout #26387

Merged
merged 2 commits into from
Mar 8, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 1 addition & 30 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
- [Portable stories](#portable-stories)
- [Project annotations are now merged instead of overwritten in composeStory](#project-annotations-are-now-merged-instead-of-overwritten-in-composestory)
- [Type change in `composeStories` API](#type-change-in-composestories-api)
- [DOM structure changed in portable stories](#dom-structure-changed-in-portable-stories)
- [Composed Vue stories are now components instead of functions](#composed-vue-stories-are-now-components-instead-of-functions)
- [Tab addons are now routed to a query parameter](#tab-addons-are-now-routed-to-a-query-parameter)
- [Default keyboard shortcuts changed](#default-keyboard-shortcuts-changed)
Expand Down Expand Up @@ -440,35 +439,6 @@ await Primary.play!(...) // if you want a runtime error when the play function d

There are plans to make the type of the play function be inferred based on your imported story's play function in a near future, so the types will be 100% accurate.

#### DOM structure changed in portable stories

The portable stories API now adds a wrapper to your stories with a unique id based on your story id, such as:

```html
<div data-story="true" id="#storybook-story-button--primary">
<!-- your story here -->
</div>
```

This means that if you take DOM snapshots of your stories, they will be affected and you will have to update them.

The id calculation is based on different heuristics based on your Meta title and Story name. When using `composeStories`, the id can be inferred automatically. However, when using `composeStory` and your story does not explicitly have a `storyName` property, the story name can't be inferred automatically. As a result, its name will be "Unnamed Story", resulting in a wrapper id like `"#storybook-story-button--unnamed-story"`. If the id matters to you and you want to fix it, you have to specify the `exportsName` property like so:

```ts
test("snapshots the story with custom id", () => {
const Primary = composeStory(
stories.Primary,
stories.default,
undefined,
// If you do not want the `unnamed-story` id, you have to pass the name of the story as a parameter
"Primary"
);

const { baseElement } = render(<Primary />);
expect(baseElement).toMatchSnapshot();
});
```

#### Composed Vue stories are now components instead of functions

`composeStory` (and `composeStories`) from `@storybook/vue3` now return Vue components rather than story functions that return components. This means that when rendering these composed stories you just pass the composed story _without_ first calling it.
Expand Down Expand Up @@ -1105,6 +1075,7 @@ const preview: Preview = {

export default preview;
```

To learn more about the available icons and their names, see the [Storybook documentation](https://storybook.js.org/docs/8.0/faq#what-icons-are-available-for-my-toolbar-or-my-addon).

#### Removals in @storybook/types
Expand Down
1 change: 0 additions & 1 deletion code/lib/preview-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ export {
filterArgTypes,
sanitizeStoryContextUpdate,
setProjectAnnotations,
getPortableStoryWrapperId,
inferControls,
userOrAutoTitleFromSpecifier,
userOrAutoTitle,
Expand Down
10 changes: 1 addition & 9 deletions code/lib/preview-api/src/modules/store/csf/portable-stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ import { normalizeProjectAnnotations } from './normalizeProjectAnnotations';

let globalProjectAnnotations: ProjectAnnotations<any> = {};

export function getPortableStoryWrapperId(storyId: string) {
return `storybook-story-${storyId}`;
}

export function setProjectAnnotations<TRenderer extends Renderer = Renderer>(
projectAnnotations: ProjectAnnotations<TRenderer> | ProjectAnnotations<TRenderer>[]
) {
Expand Down Expand Up @@ -99,11 +95,7 @@ export function composeStory<TRenderer extends Renderer = Renderer, TArgs extend
story.playFunction!({
...context,
...extraContext,
// if canvasElement is not provided, we default to the root element, which comes from a decorator
// the decorator has to be implemented in the defaultAnnotations of each integrator of portable stories
canvasElement:
extraContext?.canvasElement ??
globalThis.document?.getElementById(getPortableStoryWrapperId(context.id)),
canvasElement: extraContext?.canvasElement ?? globalThis.document?.body,
})
: undefined;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,77 +3,57 @@
exports[`Renders CSF2Secondary story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-2-secondary"
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
Children coming from story args!
</button>
</div>
Children coming from story args!
</button>
</div>
</body>
`;

exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-2-story-with-params-and-decorator"
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
foo
</button>
</div>
foo
</button>
</div>
</body>
`;

exports[`Renders CSF3Button story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-3-button"
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
foo
</button>
</div>
foo
</button>
</div>
</body>
`;

exports[`Renders CSF3ButtonWithRender story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-3-button-with-render"
>
<div>
<p
data-testid="custom-render"
>
I am a custom render function
</p>
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
foo
</button>
</div>
<div>
<p
data-testid="custom-render"
>
I am a custom render function
</p>
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
foo
</button>
</div>
</div>
</body>
Expand All @@ -82,54 +62,39 @@ exports[`Renders CSF3ButtonWithRender story 1`] = `
exports[`Renders CSF3InputFieldFilled story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-3-input-field-filled"
>
<input
data-testid="input"
/>
</div>
<input
data-testid="input"
/>
</div>
</body>
`;

exports[`Renders CSF3Primary story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-3-primary"
<button
class="storybook-button storybook-button--large storybook-button--primary"
type="button"
>
<button
class="storybook-button storybook-button--large storybook-button--primary"
type="button"
>
foo
</button>
</div>
foo
</button>
</div>
</body>
`;

exports[`Renders LoaderStory story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--loader-story"
>
<div>
<div
data-testid="loaded-data"
>
loaded data
</div>
<div
data-testid="spy-data"
>
mockFn return value
</div>
<div>
<div
data-testid="loaded-data"
>
loaded data
</div>
<div
data-testid="spy-data"
>
mockFn return value
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import React from 'react';
import {
composeStory as originalComposeStory,
composeStories as originalComposeStories,
setProjectAnnotations as originalSetProjectAnnotations,
getPortableStoryWrapperId,
} from '@storybook/preview-api';
import type {
Args,
Expand All @@ -13,7 +11,7 @@ import type {
StoriesWithPartialProps,
} from '@storybook/types';

import * as reactProjectAnnotations from './entry-preview';
import * as defaultProjectAnnotations from './entry-preview';
import type { Meta } from './public-types';
import type { ReactRenderer } from './types';

Expand All @@ -38,20 +36,6 @@ export function setProjectAnnotations(
originalSetProjectAnnotations<ReactRenderer>(projectAnnotations);
}

// This will not be necessary once we have auto preset loading
const defaultProjectAnnotations: ProjectAnnotations<ReactRenderer> = {
...reactProjectAnnotations,
decorators: [
function addStorybookId(StoryFn, { id }) {
return (
<div data-story id={getPortableStoryWrapperId(id)}>
<StoryFn />
</div>
);
},
],
};

/**
* Function that will receive a story along with meta (e.g. a default export from a .stories file)
* and optionally projectAnnotations e.g. (import * from '../.storybook/preview)
Expand Down