or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

actions.mdcli-commands.mdframework-support.mdhighlighting.mdindex.mdmanager-api.mdstory-composition.mdtesting.mdtheming.mdviewport.md

story-composition.mddocs/

0

# Story Composition

1

2

Story composition functionality for creating and testing stories outside of the Storybook environment. This is essential for unit testing, integration testing, and component validation workflows.

3

4

## Capabilities

5

6

### Compose Single Story

7

8

Creates a composed story that can be rendered and tested independently.

9

10

```typescript { .api }

11

/**

12

* Compose a single story for independent testing and rendering

13

* @param story - The story function to compose

14

* @param meta - The meta object containing story metadata

15

* @param projectAnnotations - Optional project-level annotations

16

* @returns A composed story function with additional metadata

17

*/

18

function composeStory<TRenderer, TArgs>(

19

story: Story<TRenderer, TArgs>,

20

meta: Meta<TRenderer, TArgs>,

21

projectAnnotations?: ProjectAnnotations<TRenderer>

22

): ComposedStory<TRenderer, TArgs>;

23

24

interface ComposedStory<TRenderer = unknown, TArgs = unknown> {

25

(args?: Partial<TArgs>): unknown;

26

id: string;

27

storyName: string;

28

args: TArgs;

29

parameters: Parameters;

30

argTypes: ArgTypes<TArgs>;

31

play?: PlayFunction<TRenderer, TArgs>;

32

}

33

```

34

35

**Usage Example:**

36

37

```typescript

38

import { composeStory } from "storybook/preview-api";

39

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

40

import { Button } from "./Button";

41

import type { Meta, StoryObj } from "@storybook/react";

42

43

const meta: Meta<typeof Button> = {

44

title: "Example/Button",

45

component: Button,

46

parameters: {

47

layout: "centered",

48

},

49

argTypes: {

50

backgroundColor: { control: "color" },

51

},

52

};

53

54

const Primary: StoryObj<typeof meta> = {

55

args: {

56

primary: true,

57

label: "Button",

58

},

59

};

60

61

// Compose the story for testing

62

const ComposedPrimary = composeStory(Primary, meta);

63

64

// Use in tests

65

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

66

render(<ComposedPrimary />);

67

const button = screen.getByRole("button");

68

expect(button).toHaveClass("storybook-button--primary");

69

});

70

```

71

72

### Compose All Stories

73

74

Composes all stories from a stories module for bulk testing operations.

75

76

```typescript { .api }

77

/**

78

* Compose all stories from a stories module

79

* @param module - The stories module containing meta and stories

80

* @param projectAnnotations - Optional project-level annotations

81

* @returns Object containing all composed stories indexed by story ID

82

*/

83

function composeStories<TModule extends StoriesModule>(

84

module: TModule,

85

projectAnnotations?: ProjectAnnotations<TRenderer>

86

): ComposedStoryModule<TModule>;

87

88

type ComposedStoryModule<TModule extends StoriesModule> = {

89

[K in keyof Omit<TModule, 'default'>]: TModule[K] extends Story<infer TRenderer, infer TArgs>

90

? ComposedStory<TRenderer, TArgs>

91

: never;

92

};

93

94

interface StoriesModule {

95

default: Meta;

96

[key: string]: Story | Meta;

97

}

98

```

99

100

**Usage Example:**

101

102

```typescript

103

import { composeStories } from "storybook/preview-api";

104

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

105

106

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

107

108

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

109

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

110

render(<Primary />);

111

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

112

});

113

114

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

115

render(<Secondary />);

116

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

117

});

118

});

119

```

120

121

### Set Project Annotations

122

123

Configures project-level annotations that apply to all composed stories.

124

125

```typescript { .api }

126

/**

127

* Set project-level annotations for all composed stories

128

* @param annotations - Project-wide configuration or array of configurations to merge

129

*/

130

function setProjectAnnotations<TRenderer>(

131

annotations: ProjectAnnotations<TRenderer> | ProjectAnnotations<TRenderer>[]

132

): void;

133

134

interface ProjectAnnotations<TRenderer = unknown> {

135

parameters?: Parameters;

136

decorators?: DecoratorFunction<TRenderer>[];

137

args?: Args;

138

argTypes?: ArgTypes;

139

globals?: Args;

140

globalTypes?: GlobalTypes;

141

}

142

```

143

144

**Usage Example:**

145

146

```typescript

147

import { setProjectAnnotations } from "storybook/preview-api";

148

149

// Set up global configuration

150

setProjectAnnotations({

151

parameters: {

152

backgrounds: {

153

default: "light",

154

values: [

155

{ name: "light", value: "#ffffff" },

156

{ name: "dark", value: "#333333" },

157

],

158

},

159

actions: { argTypesRegex: "^on[A-Z].*" },

160

},

161

decorators: [

162

(Story) => (

163

<div style={{ margin: "3em" }}>

164

<Story />

165

</div>

166

),

167

],

168

globals: {

169

backgrounds: { value: "light" },

170

},

171

});

172

```

173

174

## Hook API for Stories

175

176

Storybook provides React-like hooks that can be used within stories and decorators for state management and side effects.

177

178

### Story Context Hooks

179

180

```typescript { .api }

181

/**

182

* Access story arguments with update capability

183

* @returns Tuple of current args, update function, and reset function

184

*/

185

function useArgs<TArgs>(): [

186

TArgs,

187

(newArgs: Partial<TArgs>) => void,

188

(argNames?: (keyof TArgs)[]) => void

189

];

190

191

/**

192

* Access global parameters with update capability

193

* @returns Tuple of current globals and update function

194

*/

195

function useGlobals(): [Args, (newGlobals: Args) => void];

196

197

/**

198

* Access a specific story parameter

199

* @param parameterKey - The parameter key to retrieve

200

* @param defaultValue - Default value if parameter is undefined

201

* @returns The parameter value or undefined

202

*/

203

function useParameter<S>(parameterKey: string, defaultValue?: S): S | undefined;

204

205

/**

206

* Access the complete story context

207

* @returns The current story context object

208

*/

209

function useStoryContext<TRenderer>(): StoryContext<TRenderer>;

210

```

211

212

### Standard React-style Hooks

213

214

```typescript { .api }

215

function useState<S>(initialState: S | (() => S)): [S, (update: S | ((prevState: S) => S)) => void];

216

function useEffect(create: () => (() => void) | void, deps?: any[]): void;

217

function useReducer<S, A>(reducer: (state: S, action: A) => S, initialState: S): [S, (action: A) => void];

218

function useMemo<T>(nextCreate: () => T, deps?: any[]): T;

219

function useCallback<T>(callback: T, deps?: any[]): T;

220

function useRef<T>(initialValue: T): { current: T };

221

```

222

223

**Usage Example:**

224

225

```typescript

226

import { useArgs, useEffect } from "storybook/preview-api";

227

228

export const InteractiveButton: Story = {

229

render: (args) => {

230

const [{ count }, updateArgs] = useArgs();

231

232

useEffect(() => {

233

console.log("Button count changed:", count);

234

}, [count]);

235

236

return (

237

<button

238

onClick={() => updateArgs({ count: (count || 0) + 1 })}

239

{...args}

240

>

241

Clicked {count || 0} times

242

</button>

243

);

244

},

245

args: {

246

count: 0,

247

},

248

};

249

```

250

251

## Core Types

252

253

```typescript { .api }

254

interface Story<TRenderer = unknown, TArgs = unknown> {

255

(args: TArgs, context: StoryContext<TRenderer>): unknown;

256

storyName?: string;

257

parameters?: Parameters;

258

args?: Partial<TArgs>;

259

argTypes?: ArgTypes<TArgs>;

260

decorators?: DecoratorFunction<TRenderer, TArgs>[];

261

play?: PlayFunction<TRenderer, TArgs>;

262

}

263

264

interface Meta<TRenderer = unknown, TArgs = unknown> {

265

title?: string;

266

component?: unknown;

267

subcomponents?: Record<string, unknown>;

268

parameters?: Parameters;

269

args?: Partial<TArgs>;

270

argTypes?: ArgTypes<TArgs>;

271

decorators?: DecoratorFunction<TRenderer, TArgs>[];

272

loaders?: LoaderFunction<TRenderer, TArgs>[];

273

}

274

275

interface StoryContext<TRenderer = unknown> {

276

id: string;

277

name: string;

278

title: string;

279

parameters: Parameters;

280

args: Args;

281

argTypes: ArgTypes;

282

globals: Args;

283

viewMode: ViewMode;

284

loaded: Record<string, unknown>;

285

abortSignal: AbortSignal;

286

}

287

288

type DecoratorFunction<TRenderer = unknown, TArgs = unknown> = (

289

story: () => unknown,

290

context: StoryContext<TRenderer>

291

) => unknown;

292

293

type PlayFunction<TRenderer = unknown, TArgs = unknown> = (

294

context: StoryContext<TRenderer> & { canvasElement: HTMLElement }

295

) => Promise<void> | void;

296

```