CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-storybook--preview-api

Storybook's core preview API providing hooks, decorators, story composition utilities, and simulation tools for building UI components in isolation

Pending
Overview
Eval results
Files

testing-simulation.mddocs/

Testing & Simulation

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.

Capabilities

Page Lifecycle Simulation

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();
});

Playwright Test Integration

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');
});

CSF Factory Utilities

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 }
  };
};

Mock Utilities

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' });
});

Testing Utilities for Stories

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;
}

Types & Interfaces

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

docs

decorators.md

hooks.md

index.md

preview-system.md

story-composition.md

story-store.md

testing-simulation.md

tile.json