or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdportable-stories.mdpreview-configuration.mdreact-renderer.mdstory-types.mdtesting-integration.md

portable-stories.mddocs/

0

# Portable Stories

1

2

Functionality for using Storybook stories outside of the Storybook environment, particularly useful for unit testing, component documentation, and integration testing. Portable stories allow you to reuse your story definitions in Jest, Vitest, Playwright, and other testing frameworks.

3

4

## Type Dependencies

5

6

The portable stories API depends on core Storybook types:

7

8

```typescript

9

// Main portable stories exports

10

import { setProjectAnnotations, composeStory, composeStories } from "@storybook/react";

11

12

// Core Storybook internal types

13

import type {

14

Args,

15

NamedOrDefaultProjectAnnotations,

16

NormalizedProjectAnnotations,

17

ProjectAnnotations,

18

StoryAnnotationsOrFn,

19

Store_CSFExports,

20

StoriesWithPartialProps,

21

ComposedStoryFn

22

} from "storybook/internal/types";

23

24

// React renderer type (see React Renderer Types documentation)

25

import type { ReactRenderer } from "./react-renderer.md#react-renderer-interface";

26

27

// Meta type (see Story Types & Metadata documentation)

28

import type { Meta } from "./story-types.md#meta-type";

29

```

30

31

## Capabilities

32

33

### Set Project Annotations

34

35

Configures global project settings for story composition, typically used in test setup files.

36

37

```typescript { .api }

38

/**

39

* Sets the global configuration for story composition.

40

* Should be called once in your test setup to apply global decorators, parameters, and other configurations.

41

*

42

* @param projectAnnotations - Global project configuration (e.g., from .storybook/preview.ts)

43

* @returns Normalized project annotations for the React renderer

44

*/

45

function setProjectAnnotations(

46

projectAnnotations:

47

| NamedOrDefaultProjectAnnotations<any>

48

| NamedOrDefaultProjectAnnotations<any>[]

49

): NormalizedProjectAnnotations<ReactRenderer>;

50

```

51

52

**Usage Example:**

53

54

```typescript

55

// setup-tests.ts

56

import { setProjectAnnotations } from "@storybook/react";

57

import * as projectAnnotations from "../.storybook/preview";

58

59

// Apply global configuration to all composed stories

60

setProjectAnnotations(projectAnnotations);

61

```

62

63

### Compose Story

64

65

Creates a composed story component from a story definition and meta, ready for use in testing or other contexts.

66

67

```typescript { .api }

68

/**

69

* Composes a single story with its metadata and configuration into a React component.

70

* The resulting component can be rendered directly in tests or other environments.

71

*

72

* @param story - Story definition (object or function)

73

* @param componentAnnotations - Component metadata (Meta export)

74

* @param projectAnnotations - Optional project configuration (if not set globally)

75

* @param exportsName - Optional name for the story (for debugging)

76

* @returns Composed story component ready for rendering

77

*/

78

function composeStory<TArgs extends Args = Args>(

79

story: StoryAnnotationsOrFn<ReactRenderer, TArgs>,

80

componentAnnotations: Meta<TArgs | any>,

81

projectAnnotations?: ProjectAnnotations<ReactRenderer>,

82

exportsName?: string

83

): ComposedStoryFn<ReactRenderer, Partial<TArgs>>;

84

```

85

86

**Usage Example:**

87

88

```typescript

89

// Button.test.tsx

90

import { render, screen } from "@testing-library/react";

91

import { composeStory } from "@storybook/react";

92

import Meta, { Primary, Secondary } from "./Button.stories";

93

94

// Compose individual stories

95

const PrimaryButton = composeStory(Primary, Meta);

96

const SecondaryButton = composeStory(Secondary, Meta);

97

98

test("renders primary button", () => {

99

render(<PrimaryButton />);

100

expect(screen.getByRole("button")).toHaveClass("primary");

101

});

102

103

test("renders secondary button with custom props", () => {

104

render(<SecondaryButton label="Custom Label" />);

105

expect(screen.getByText("Custom Label")).toBeInTheDocument();

106

});

107

108

// Test with overridden args

109

test("renders button with specific size", () => {

110

render(<PrimaryButton size="large" />);

111

expect(screen.getByRole("button")).toHaveClass("large");

112

});

113

```

114

115

### Compose Stories

116

117

Creates composed components for all stories in a story file, providing an object with all stories ready for testing.

118

119

```typescript { .api }

120

/**

121

* Composes all stories from a story file into an object of React components.

122

* Each story becomes a component that can be rendered independently.

123

*

124

* @param csfExports - All exports from a .stories file (import * as stories)

125

* @param projectAnnotations - Optional project configuration (if not set globally)

126

* @returns Object containing all composed stories, excluding CSF metadata

127

*/

128

function composeStories<TModule extends Store_CSFExports<ReactRenderer, any>>(

129

csfExports: TModule,

130

projectAnnotations?: ProjectAnnotations<ReactRenderer>

131

): Omit<StoriesWithPartialProps<ReactRenderer, TModule>, keyof Store_CSFExports>;

132

```

133

134

**Usage Example:**

135

136

```typescript

137

// Button.test.tsx

138

import { render, screen } from "@testing-library/react";

139

import { composeStories } from "@storybook/react";

140

import * as stories from "./Button.stories";

141

142

// Compose all stories from the file

143

const { Primary, Secondary, Large, Small } = composeStories(stories);

144

145

describe("Button Stories", () => {

146

test("Primary story renders correctly", () => {

147

render(<Primary />);

148

expect(screen.getByRole("button")).toHaveClass("primary");

149

});

150

151

test("Secondary story renders correctly", () => {

152

render(<Secondary />);

153

expect(screen.getByRole("button")).toHaveClass("secondary");

154

});

155

156

// Test all stories programmatically

157

Object.entries(composeStories(stories)).forEach(([name, Story]) => {

158

test(`${name} story renders without errors`, () => {

159

render(<Story />);

160

expect(screen.getByRole("button")).toBeInTheDocument();

161

});

162

});

163

});

164

```

165

166

### Advanced Testing Patterns

167

168

**Testing with React Testing Library:**

169

170

```typescript

171

import { render, screen, fireEvent } from "@testing-library/react";

172

import { composeStory } from "@storybook/react";

173

import Meta, { Interactive } from "./Button.stories";

174

175

const InteractiveButton = composeStory(Interactive, Meta);

176

177

test("button handles click events", async () => {

178

const mockFn = vi.fn();

179

render(<InteractiveButton onClick={mockFn} />);

180

181

fireEvent.click(screen.getByRole("button"));

182

expect(mockFn).toHaveBeenCalledTimes(1);

183

});

184

```

185

186

**Testing with Custom Context:**

187

188

```typescript

189

import { render } from "@testing-library/react";

190

import { composeStory } from "@storybook/react";

191

import { ThemeProvider } from "./ThemeProvider";

192

import Meta, { Themed } from "./Button.stories";

193

194

const ThemedButton = composeStory(Themed, Meta);

195

196

test("button renders with custom theme", () => {

197

render(

198

<ThemeProvider theme="dark">

199

<ThemedButton />

200

</ThemeProvider>

201

);

202

// Test themed button behavior

203

});

204

```

205

206

**Playwright Integration:**

207

208

```typescript

209

import { test, expect } from "@playwright/test";

210

import { composeStories } from "@storybook/react";

211

import * as stories from "./Button.stories";

212

213

const { Primary } = composeStories(stories);

214

215

test("visual regression test", async ({ page }) => {

216

// Mount the composed story in Playwright

217

await page.goto("/test-page");

218

await page.evaluate(() => {

219

// Render the composed story

220

const root = ReactDOM.createRoot(document.getElementById("root"));

221

root.render(React.createElement(Primary));

222

});

223

224

await expect(page).toHaveScreenshot("primary-button.png");

225

});

226

```

227

228

## Internal Default Annotations

229

230

Default project configuration used internally by the React renderer.

231

232

```typescript { .api }

233

/**

234

* Internal default project annotations for React renderer.

235

* Includes React-specific rendering configuration and compatibility layers.

236

*/

237

const INTERNAL_DEFAULT_PROJECT_ANNOTATIONS: ProjectAnnotations<ReactRenderer>;

238

```

239

240

This constant is used internally and typically doesn't need to be accessed directly, but it provides the base React rendering configuration that can be extended with custom project annotations.

241

242

## Type Definitions

243

244

### Composed Story Function Type

245

246

```typescript { .api }

247

/**

248

* Type for composed story functions that can be rendered as React components.

249

* Supports partial args and maintains type safety.

250

*/

251

type ComposedStoryFn<TRenderer extends Renderer, TArgs> = React.ComponentType<Partial<TArgs>> & {

252

args: TArgs;

253

argTypes: ArgTypes;

254

parameters: Parameters;

255

storyName: string;

256

};

257

```

258

259

### Story Annotations Type

260

261

```typescript { .api }

262

/**

263

* Type for story definitions that can be functions or objects.

264

* Used by composeStory for flexible story input.

265

*/

266

type StoryAnnotationsOrFn<TRenderer extends Renderer, TArgs> =

267

| StoryAnnotations<TRenderer, TArgs>

268

| AnnotatedStoryFn<TRenderer, TArgs>;

269

```