or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli-builders.mddecorators.mdframework-config.mdindex.mdportable-stories.mdstory-types.mdtemplate-utilities.md

portable-stories.mddocs/

0

# Portable Stories

1

2

Utilities for using Storybook stories outside of the Storybook environment, such as in testing frameworks and other applications.

3

4

## Capabilities

5

6

### setProjectAnnotations Function

7

8

Sets global project annotations (preview configuration) for using stories outside of Storybook. This should be run once to apply global decorators, parameters, and other 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

* @returns Normalized project annotations

20

*/

21

declare function setProjectAnnotations(

22

projectAnnotations:

23

| NamedOrDefaultProjectAnnotations<any>

24

| NamedOrDefaultProjectAnnotations<any>[]

25

): NormalizedProjectAnnotations<AngularRenderer>;

26

```

27

28

## Usage Examples

29

30

### Testing with Jest

31

32

Set up portable stories for Jest testing:

33

34

```typescript

35

// setup-tests.ts

36

import { setProjectAnnotations } from '@storybook/angular';

37

import { TestBed } from '@angular/core/testing';

38

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

39

40

// Import your global storybook preview configuration

41

import globalStorybookConfig from '../.storybook/preview';

42

43

// Set up global storybook configuration

44

setProjectAnnotations(globalStorybookConfig);

45

46

// Configure Angular TestBed

47

beforeEach(() => {

48

TestBed.configureTestingModule({

49

imports: [BrowserAnimationsModule],

50

});

51

});

52

```

53

54

**Using stories in Jest tests:**

55

56

```typescript

57

// button.component.spec.ts

58

import { ComponentFixture, TestBed } from '@angular/core/testing';

59

import { composeStories } from '@storybook/testing-angular';

60

import * as stories from './button.stories';

61

62

// Compose all stories from the story file

63

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

64

65

describe('ButtonComponent', () => {

66

let fixture: ComponentFixture<any>;

67

68

it('should render primary button correctly', async () => {

69

// Use the Primary story

70

const component = TestBed.createComponent(Primary.component);

71

component.componentInstance = { ...Primary.args };

72

fixture = component;

73

fixture.detectChanges();

74

75

expect(fixture.nativeElement.textContent).toContain('Button');

76

expect(fixture.nativeElement.querySelector('.primary')).toBeTruthy();

77

});

78

79

it('should handle click events', async () => {

80

const component = TestBed.createComponent(Primary.component);

81

const clickSpy = jest.fn();

82

83

component.componentInstance = {

84

...Primary.args,

85

onClick: clickSpy

86

};

87

88

fixture = component;

89

fixture.detectChanges();

90

91

fixture.nativeElement.querySelector('button').click();

92

expect(clickSpy).toHaveBeenCalled();

93

});

94

});

95

```

96

97

### Testing with Playwright

98

99

Use stories for end-to-end testing:

100

101

```typescript

102

// setup-e2e.ts

103

import { setProjectAnnotations } from '@storybook/angular';

104

import globalStorybookConfig from '../.storybook/preview';

105

106

setProjectAnnotations(globalStorybookConfig);

107

```

108

109

```typescript

110

// button.e2e.spec.ts

111

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

112

import { composeStories } from '@storybook/testing-angular';

113

import * as stories from './button.stories';

114

115

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

116

117

test.describe('Button Component E2E', () => {

118

test('primary button should be clickable', async ({ page }) => {

119

// Navigate to a page that renders the Primary story

120

await page.goto('/storybook-iframe.html?id=button--primary');

121

122

const button = page.locator('button');

123

await expect(button).toBeVisible();

124

await expect(button).toHaveClass(/primary/);

125

126

await button.click();

127

// Assert expected behavior after click

128

});

129

});

130

```

131

132

### Snapshot Testing

133

134

Use stories for visual regression testing:

135

136

```typescript

137

// button.snapshot.spec.ts

138

import { ComponentFixture, TestBed } from '@angular/core/testing';

139

import { composeStories } from '@storybook/testing-angular';

140

import * as stories from './button.stories';

141

142

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

143

144

describe('Button Snapshots', () => {

145

let fixture: ComponentFixture<any>;

146

147

const renderStory = (story: any) => {

148

const component = TestBed.createComponent(story.component);

149

component.componentInstance = { ...story.args };

150

fixture = component;

151

fixture.detectChanges();

152

return fixture.nativeElement;

153

};

154

155

it('should match primary button snapshot', () => {

156

const element = renderStory(Primary);

157

expect(element).toMatchSnapshot();

158

});

159

160

it('should match secondary button snapshot', () => {

161

const element = renderStory(Secondary);

162

expect(element).toMatchSnapshot();

163

});

164

165

it('should match large button snapshot', () => {

166

const element = renderStory(Large);

167

expect(element).toMatchSnapshot();

168

});

169

170

it('should match small button snapshot', () => {

171

const element = renderStory(Small);

172

expect(element).toMatchSnapshot();

173

});

174

});

175

```

176

177

### Custom Testing Utilities

178

179

Create reusable testing utilities with portable stories:

180

181

```typescript

182

// story-test-utils.ts

183

import { ComponentFixture, TestBed } from '@angular/core/testing';

184

import { setProjectAnnotations } from '@storybook/angular';

185

import { composeStories } from '@storybook/testing-angular';

186

import globalStorybookConfig from '../.storybook/preview';

187

188

// Set up global configuration once

189

setProjectAnnotations(globalStorybookConfig);

190

191

export interface StoryTestOptions {

192

story: any;

193

props?: Record<string, any>;

194

detectChanges?: boolean;

195

}

196

197

export class StoryTestHelper {

198

private fixture: ComponentFixture<any>;

199

200

static create(options: StoryTestOptions): StoryTestHelper {

201

const helper = new StoryTestHelper();

202

helper.render(options);

203

return helper;

204

}

205

206

private render(options: StoryTestOptions): void {

207

const component = TestBed.createComponent(options.story.component);

208

component.componentInstance = {

209

...options.story.args,

210

...options.props

211

};

212

213

this.fixture = component;

214

215

if (options.detectChanges !== false) {

216

this.fixture.detectChanges();

217

}

218

}

219

220

get element(): HTMLElement {

221

return this.fixture.nativeElement;

222

}

223

224

get component(): any {

225

return this.fixture.componentInstance;

226

}

227

228

get fixture(): ComponentFixture<any> {

229

return this.fixture;

230

}

231

232

query(selector: string): HTMLElement | null {

233

return this.element.querySelector(selector);

234

}

235

236

queryAll(selector: string): NodeList {

237

return this.element.querySelectorAll(selector);

238

}

239

240

updateProps(props: Record<string, any>): void {

241

Object.assign(this.component, props);

242

this.fixture.detectChanges();

243

}

244

245

triggerEvent(selector: string, eventType: string, eventData?: any): void {

246

const element = this.query(selector);

247

if (element) {

248

const event = new Event(eventType);

249

if (eventData) {

250

Object.assign(event, eventData);

251

}

252

element.dispatchEvent(event);

253

this.fixture.detectChanges();

254

}

255

}

256

}

257

258

// Usage example:

259

// const helper = StoryTestHelper.create({ story: Primary });

260

// expect(helper.query('button')).toBeTruthy();

261

// helper.updateProps({ disabled: true });

262

// expect(helper.query('button')).toHaveAttribute('disabled');

263

```

264

265

### Integration Testing

266

267

Test complete user workflows using multiple stories:

268

269

```typescript

270

// user-workflow.spec.ts

271

import { TestBed } from '@angular/core/testing';

272

import { setProjectAnnotations } from '@storybook/angular';

273

import { composeStories } from '@storybook/testing-angular';

274

import * as formStories from './form.stories';

275

import * as buttonStories from './button.stories';

276

import * as modalStories from './modal.stories';

277

import globalStorybookConfig from '../.storybook/preview';

278

279

setProjectAnnotations(globalStorybookConfig);

280

281

const { DefaultForm } = composeStories(formStories);

282

const { Primary: PrimaryButton } = composeStories(buttonStories);

283

const { ConfirmationModal } = composeStories(modalStories);

284

285

describe('User Registration Workflow', () => {

286

it('should complete user registration flow', async () => {

287

// Step 1: Render registration form

288

const formComponent = TestBed.createComponent(DefaultForm.component);

289

formComponent.componentInstance = { ...DefaultForm.args };

290

const formFixture = formComponent;

291

formFixture.detectChanges();

292

293

// Step 2: Fill out form

294

const emailInput = formFixture.nativeElement.querySelector('input[type="email"]');

295

const passwordInput = formFixture.nativeElement.querySelector('input[type="password"]');

296

297

emailInput.value = 'test@example.com';

298

emailInput.dispatchEvent(new Event('input'));

299

300

passwordInput.value = 'password123';

301

passwordInput.dispatchEvent(new Event('input'));

302

303

formFixture.detectChanges();

304

305

// Step 3: Click submit button

306

const submitButton = formFixture.nativeElement.querySelector('button[type="submit"]');

307

expect(submitButton).not.toHaveAttribute('disabled');

308

309

submitButton.click();

310

formFixture.detectChanges();

311

312

// Step 4: Verify confirmation modal appears

313

const modalComponent = TestBed.createComponent(ConfirmationModal.component);

314

modalComponent.componentInstance = { ...ConfirmationModal.args };

315

const modalFixture = modalComponent;

316

modalFixture.detectChanges();

317

318

expect(modalFixture.nativeElement).toContainText('Registration Successful');

319

});

320

});

321

```

322

323

## Setup Patterns

324

325

### Global Setup for Testing Framework

326

327

```typescript

328

// jest.setup.ts or vitest.setup.ts

329

import { setProjectAnnotations } from '@storybook/angular';

330

import { TestBed } from '@angular/core/testing';

331

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

332

import { CommonModule } from '@angular/common';

333

334

// Import your global storybook configuration

335

import globalStorybookConfig from '../.storybook/preview';

336

337

// Apply global storybook configuration

338

setProjectAnnotations(globalStorybookConfig);

339

340

// Global Angular TestBed configuration

341

beforeEach(async () => {

342

await TestBed.configureTestingModule({

343

imports: [

344

CommonModule,

345

BrowserAnimationsModule,

346

],

347

teardown: { destroyAfterEach: true },

348

}).compileComponents();

349

});

350

```

351

352

### Per-Test Setup

353

354

```typescript

355

// individual-test.spec.ts

356

import { setProjectAnnotations } from '@storybook/angular';

357

import { TestBed } from '@angular/core/testing';

358

359

// Test-specific configuration

360

beforeEach(() => {

361

setProjectAnnotations([

362

// Base global config

363

require('../.storybook/preview').default,

364

// Test-specific overrides

365

{

366

parameters: {

367

// Override parameters for this test suite

368

backgrounds: { default: 'light' },

369

},

370

decorators: [

371

// Additional test-specific decorators

372

],

373

},

374

]);

375

});

376

```

377

378

## Best Practices

379

380

### Configuration Management

381

382

- Set up `setProjectAnnotations` once in your test setup file

383

- Import the exact same preview configuration used by Storybook

384

- Use array format for multiple configuration sources when needed

385

386

### Performance Optimization

387

388

- Avoid calling `setProjectAnnotations` repeatedly in individual tests

389

- Configure TestBed once per test suite rather than per test

390

- Use `composeStories` to precompile all stories from a file

391

392

### Type Safety

393

394

- Import story types to maintain type safety in tests

395

- Use TypeScript strict mode for better error detection

396

- Leverage Angular's dependency injection in test scenarios

397

398

### Integration with CI/CD

399

400

- Use portable stories for visual regression testing

401

- Include story-based tests in your CI pipeline

402

- Generate test coverage reports that include story usage