Storybook's core preview API providing hooks, decorators, story composition utilities, and simulation tools for building UI components in isolation
—
Tools for simulating DOM events, page lifecycle, and creating test utilities. These functions are essential for testing stories and addons in various environments, enabling comprehensive testing scenarios and cross-platform compatibility.
Functions for simulating browser page lifecycle events in testing environments.
/**
* Simulates complete page loading with script execution and DOM setup
* @param container - DOM element to simulate page load within
*/
function simulatePageLoad(container: Element): void;
/**
* Triggers DOMContentLoaded event for testing DOM-dependent code
*/
function simulateDOMContentLoaded(): void;Usage Examples:
import { simulatePageLoad, simulateDOMContentLoaded } from "@storybook/preview-api";
// Simulate page load in a container
test('component initializes after page load', () => {
const container = document.createElement('div');
document.body.appendChild(container);
// Add your component to the container
container.innerHTML = '<my-component></my-component>';
// Simulate page load to trigger initialization scripts
simulatePageLoad(container);
// Verify component initialized correctly
expect(container.querySelector('my-component')).toHaveClass('initialized');
});
// Simulate DOMContentLoaded for testing initialization
test('library initializes on DOMContentLoaded', () => {
const mockInit = jest.fn();
document.addEventListener('DOMContentLoaded', mockInit);
// Trigger the event
simulateDOMContentLoaded();
expect(mockInit).toHaveBeenCalled();
});Utilities for creating Playwright test fixtures with Storybook story mounting capabilities.
/**
* Creates Playwright test utilities with story mounting support
* @param baseTest - Base Playwright test fixture
* @returns Extended test fixture with mount capabilities
*/
function createPlaywrightTest<TFixture>(baseTest: TFixture): PlaywrightTestExtended<TFixture>;
interface PlaywrightTestExtended<TFixture> extends TFixture {
/**
* Mount a composed story in the test page
* @param story - Composed story function
* @param options - Mounting options
* @returns Promise resolving to mounted component locator
*/
mount(story: ComposedStoryFn, options?: MountOptions): Promise<Locator>;
/**
* Mount JSX/React element in the test page
* @param component - React element to mount
* @param options - Mounting options
* @returns Promise resolving to mounted component locator
*/
mountComponent(component: ReactElement, options?: MountOptions): Promise<Locator>;
}
interface MountOptions {
/** Additional props to pass to the story/component */
props?: Record<string, any>;
/** Container selector or element */
container?: string | Element;
/** Wait for specific conditions after mounting */
waitFor?: () => Promise<void>;
}Usage Examples:
import { test, expect } from '@playwright/test';
import { createPlaywrightTest } from '@storybook/preview-api';
import { composeStory } from '@storybook/preview-api';
import * as ButtonStories from './Button.stories';
// Create extended test with story mounting
const storyTest = createPlaywrightTest(test);
// Compose story
const Primary = composeStory(ButtonStories.Primary, ButtonStories.default);
storyTest('Primary button is interactive', async ({ mount, page }) => {
// Mount the story
const component = await mount(Primary, {
props: { label: 'Test Button' }
});
// Test interactions
await expect(component).toBeVisible();
await component.click();
// Verify behavior
await expect(component).toHaveClass('clicked');
});
storyTest('Button with custom args', async ({ mount }) => {
// Mount with custom args
const component = await mount(Primary, {
props: {
label: 'Custom Label',
disabled: true,
variant: 'secondary'
}
});
await expect(component).toBeDisabled();
await expect(component).toHaveText('Custom Label');
});Functions for extracting annotations and metadata from CSF stories for testing.
/**
* Gets factory annotations for CSF story testing and composition
* @param story - Story object or annotations
* @param meta - Component meta annotations (optional)
* @param projectAnnotations - Project-level annotations (optional)
* @returns Combined annotations for story factory
*/
function getCsfFactoryAnnotations<TRenderer>(
story: StoryAnnotations<TRenderer> | Story<TRenderer>,
meta?: ComponentAnnotations<TRenderer>,
projectAnnotations?: ProjectAnnotations<TRenderer>
): FactoryAnnotations<TRenderer>;
interface FactoryAnnotations<TRenderer> {
/** Combined story parameters */
parameters: Parameters;
/** Combined decorators */
decorators: DecoratorFunction<TRenderer>[];
/** Combined args */
args: Args;
/** Combined arg types */
argTypes: ArgTypes;
/** Render function */
render?: Function;
/** Play function */
play?: Function;
}Usage Examples:
import { getCsfFactoryAnnotations } from "@storybook/preview-api";
import * as ButtonStories from './Button.stories';
// Extract annotations for testing
const annotations = getCsfFactoryAnnotations(
ButtonStories.Primary,
ButtonStories.default,
globalConfig
);
// Use annotations in test setup
test('story has correct parameters', () => {
expect(annotations.parameters.docs?.description?.story).toBeDefined();
expect(annotations.args.primary).toBe(true);
});
// Create test utilities from annotations
const createTestStory = (overrides = {}) => {
return {
...annotations,
args: { ...annotations.args, ...overrides }
};
};Testing utilities for mocking Storybook's communication system.
/**
* Creates a mock communication channel for testing addons and decorators
* @returns Mock channel with event emission and subscription
*/
function mockChannel(): MockChannel;
interface MockChannel {
/**
* Emit an event with data
* @param eventId - Event identifier
* @param args - Event data
*/
emit(eventId: string, ...args: any[]): void;
/**
* Subscribe to events
* @param eventId - Event identifier
* @param listener - Event handler function
*/
on(eventId: string, listener: Function): void;
/**
* Unsubscribe from events
* @param eventId - Event identifier
* @param listener - Event handler function to remove
*/
off(eventId: string, listener: Function): void;
/**
* Get all emitted events (for testing)
* @returns Array of emitted events
*/
getEmittedEvents(): EmittedEvent[];
/**
* Clear all emitted events history
*/
clearEmittedEvents(): void;
}
interface EmittedEvent {
eventId: string;
args: any[];
timestamp: number;
}Usage Examples:
import { mockChannel } from "@storybook/preview-api";
// Create mock channel for testing
const channel = mockChannel();
// Test addon communication
test('addon emits correct events', () => {
const addon = new MyAddon(channel);
addon.doSomething();
const events = channel.getEmittedEvents();
expect(events).toHaveLength(1);
expect(events[0].eventId).toBe('addon-action');
expect(events[0].args[0]).toEqual({ type: 'success' });
});
// Test event subscriptions
test('addon responds to events', () => {
const mockHandler = jest.fn();
const addon = new MyAddon(channel);
channel.on('external-event', mockHandler);
channel.emit('external-event', { data: 'test' });
expect(mockHandler).toHaveBeenCalledWith({ data: 'test' });
});Helper functions for testing story behavior and composition.
/**
* Create test harness for story testing
* @param story - Story to test
* @param options - Test configuration options
* @returns Test harness with utilities
*/
function createStoryTestHarness<TRenderer>(
story: Story<TRenderer>,
options?: TestHarnessOptions
): StoryTestHarness<TRenderer>;
/**
* Creates mock channel implementation for testing (moved from deprecated addon API)
* @returns Mock channel with event tracking
*/
function mockChannel(): MockChannel;
interface TestHarnessOptions {
/** Mock implementations for dependencies */
mocks?: Record<string, any>;
/** Custom render container */
container?: HTMLElement;
/** Global test configuration */
globals?: Args;
}
interface StoryTestHarness<TRenderer> {
/**
* Render the story with given args
* @param args - Args to pass to story
* @returns Promise resolving to rendered result
*/
render(args?: Args): Promise<any>;
/**
* Update story args and re-render
* @param newArgs - Args to update
*/
updateArgs(newArgs: Args): Promise<void>;
/**
* Execute story play function
* @returns Promise resolving when play function completes
*/
executePlay(): Promise<void>;
/**
* Get current story context
* @returns Current story context
*/
getContext(): StoryContext<TRenderer>;
/**
* Clean up test harness
*/
cleanup(): void;
}interface ComposedStoryFn<TRenderer = any, TArgs = any> {
/** Execute story with optional args override */
(args?: Partial<TArgs>): any;
/** Story metadata */
storyName?: string;
args?: TArgs;
parameters?: Parameters;
argTypes?: ArgTypes;
id?: string;
}
interface PlayFunctionContext<TRenderer = any, TArgs = any> {
/** Story args */
args: TArgs;
/** Canvas element for interactions */
canvasElement: HTMLElement;
/** Step function for organizing test steps */
step: (label: string, play: (context: PlayFunctionContext<TRenderer, TArgs>) => Promise<void> | void) => Promise<void>;
/** Global values */
globals: Args;
/** Hooks context */
hooks: HooksContext<TRenderer>;
}
interface StoryAnnotations<TRenderer = any, TArgs = any> {
args?: Partial<TArgs>;
argTypes?: ArgTypes;
parameters?: Parameters;
decorators?: DecoratorFunction<TRenderer>[];
render?: (args: TArgs, context: StoryContext<TRenderer>) => any;
play?: (context: PlayFunctionContext<TRenderer, TArgs>) => Promise<void> | void;
}
interface ComponentAnnotations<TRenderer = any, TArgs = any> {
title?: string;
component?: any;
args?: Partial<TArgs>;
argTypes?: ArgTypes;
parameters?: Parameters;
decorators?: DecoratorFunction<TRenderer>[];
render?: (args: TArgs, context: StoryContext<TRenderer>) => any;
}Install with Tessl CLI
npx tessl i tessl/npm-storybook--preview-api