0
# Portable Story Composition
1
2
Create reusable story components for testing and external usage, allowing Storybook stories to be composed and rendered outside of the Storybook environment. This enables story reuse in unit tests, documentation, and other applications.
3
4
## Capabilities
5
6
### Set Project Annotations
7
8
Configure global Storybook project settings that apply to all composed stories, including decorators, parameters, and global configuration.
9
10
```typescript { .api }
11
/**
12
* Function that sets the globalConfig of your Storybook. The global config is the preview module of
13
* your .storybook folder.
14
*
15
* It should be run a single time, so that your global config (e.g. decorators) is applied to your
16
* stories when using composeStories or composeStory.
17
*
18
* @param projectAnnotations - E.g. (import projectAnnotations from './.storybook/preview')
19
*/
20
function setProjectAnnotations(
21
projectAnnotations: NamedOrDefaultProjectAnnotations<any> | NamedOrDefaultProjectAnnotations<any>[]
22
): NormalizedProjectAnnotations<VueRenderer>;
23
```
24
25
**Usage Example:**
26
27
```typescript
28
// setup-file.js
29
import { setProjectAnnotations } from "@storybook/vue3";
30
import projectAnnotations from "./.storybook/preview";
31
32
setProjectAnnotations(projectAnnotations);
33
```
34
35
### Compose Single Story
36
37
Create a composed component from a single story and its metadata, combining all args, parameters, decorators, and configuration into a renderable Vue component.
38
39
```typescript { .api }
40
/**
41
* Function that will receive a story along with meta (e.g. a default export from a .stories file)
42
* and optionally projectAnnotations e.g. (import * from '../.storybook/preview) and will return a
43
* composed component that has all args/parameters/decorators/etc combined and applied to it.
44
*
45
* It's very useful for reusing a story in scenarios outside of Storybook like unit testing.
46
*
47
* @param story - The story object or function to compose
48
* @param componentAnnotations - E.g. (import Meta from './Button.stories')
49
* @param [projectAnnotations] - E.g. (import * as projectAnnotations from '../.storybook/preview')
50
* this can be applied automatically if you use setProjectAnnotations in your setup files.
51
* @param [exportsName] - In case your story does not contain a name and you want it to have a name.
52
*/
53
function composeStory<TArgs extends Args = Args>(
54
story: StoryAnnotationsOrFn<VueRenderer, TArgs>,
55
componentAnnotations: Meta<TArgs | any>,
56
projectAnnotations?: ProjectAnnotations<VueRenderer>,
57
exportsName?: string
58
): JSXAble<ComposedStoryFn<VueRenderer, Partial<TArgs>>>;
59
```
60
61
**Usage Example:**
62
63
```typescript
64
import { render } from "@testing-library/vue";
65
import { composeStory } from "@storybook/vue3";
66
import Meta, { Primary as PrimaryStory } from "./Button.stories";
67
68
const Primary = composeStory(PrimaryStory, Meta);
69
70
test("renders primary button with Hello World", () => {
71
const { getByText } = render(Primary, { props: { label: "Hello world" } });
72
expect(getByText(/Hello world/i)).not.toBeNull();
73
});
74
```
75
76
### Compose Multiple Stories
77
78
Create composed components from all stories in a story module, returning an object with all stories as renderable Vue components.
79
80
```typescript { .api }
81
/**
82
* Function that will receive a stories import (e.g. import * as stories from './Button.stories')
83
* and optionally projectAnnotations (e.g. import * from '../.storybook/preview') and will return
84
* an object containing all the stories passed, but now as a composed component that has all
85
* args/parameters/decorators/etc combined and applied to it.
86
*
87
* It's very useful for reusing stories in scenarios outside of Storybook like unit testing.
88
*
89
* @param csfExports - E.g. (import * as stories from './Button.stories')
90
* @param [projectAnnotations] - E.g. (import * as projectAnnotations from '../.storybook/preview')
91
* this can be applied automatically if you use setProjectAnnotations in your setup files.
92
*/
93
function composeStories<TModule extends Store_CSFExports<VueRenderer, any>>(
94
csfExports: TModule,
95
projectAnnotations?: ProjectAnnotations<VueRenderer>
96
): MapToJSXAble<Omit<StoriesWithPartialProps<VueRenderer, TModule>, keyof Store_CSFExports>>;
97
```
98
99
**Usage Example:**
100
101
```typescript
102
import { render } from "@testing-library/vue";
103
import { composeStories } from "@storybook/vue3";
104
import * as stories from "./Button.stories";
105
106
const { Primary, Secondary } = composeStories(stories);
107
108
test("renders primary button with Hello World", () => {
109
const { getByText } = render(Primary, { props: { label: "Hello world" } });
110
expect(getByText(/Hello world/i)).not.toBeNull();
111
});
112
113
test("renders secondary button", () => {
114
const { getByRole } = render(Secondary);
115
expect(getByRole("button")).toBeInTheDocument();
116
});
117
```
118
119
## Advanced Usage
120
121
### Testing with Composed Stories
122
123
```typescript
124
import { render, fireEvent } from "@testing-library/vue";
125
import { composeStories } from "@storybook/vue3";
126
import * as stories from "./Button.stories";
127
128
const { Primary, WithAction } = composeStories(stories);
129
130
describe("Button Component", () => {
131
test("primary button renders correctly", () => {
132
const { getByRole } = render(Primary);
133
const button = getByRole("button");
134
expect(button).toHaveClass("btn-primary");
135
});
136
137
test("button click triggers action", async () => {
138
const onClickSpy = vi.fn();
139
const { getByRole } = render(WithAction, {
140
props: { onClick: onClickSpy },
141
});
142
143
const button = getByRole("button");
144
await fireEvent.click(button);
145
expect(onClickSpy).toHaveBeenCalled();
146
});
147
});
148
```
149
150
### Story Composition with Custom Args
151
152
```typescript
153
import { composeStory } from "@storybook/vue3";
154
import Meta, { Default } from "./Button.stories";
155
156
const CustomButton = composeStory(Default, Meta);
157
158
// Use with custom props
159
const wrapper = render(CustomButton, {
160
props: {
161
label: "Custom Label",
162
size: "large",
163
variant: "danger",
164
},
165
});
166
```
167
168
## Type Definitions
169
170
```typescript { .api }
171
type JSXAble<TElement> = TElement & {
172
new (...args: any[]): any;
173
$props: any;
174
};
175
176
type MapToJSXAble<T> = {
177
[K in keyof T]: JSXAble<T[K]>;
178
};
179
180
interface ComposedStoryFn<TRenderer extends Renderer, TArgs> {
181
(args?: Partial<TArgs>): TRenderer['storyResult'];
182
storyName?: string;
183
args?: TArgs;
184
parameters?: Parameters;
185
argTypes?: ArgTypes;
186
}
187
```