or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

app-setup.mdcomponent-testing.mdindex.mdportable-stories.mdstory-types.md

portable-stories.mddocs/

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

```