CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-storybook--testing-library

Instrumented version of Testing Library for Storybook Interactions addon

Pending
Overview
Eval results
Files

configuration.mddocs/

Configuration and Utilities

Configuration options and utility functions for customizing Testing Library behavior and building custom queries. These utilities provide advanced functionality for debugging, configuration, and extending the library's capabilities.

Capabilities

Configuration

Global configuration options for customizing Testing Library behavior.

/**
 * Configure global Testing Library options
 * @param options - Configuration options to set
 */
function configure(options: ConfigureOptions): void;

/**
 * Get current Testing Library configuration
 * @returns Current configuration object
 */
function getConfig(): Config;

/**
 * Configuration options for Testing Library
 */
interface ConfigureOptions {
  /** Custom attribute to use for test IDs (default: 'data-testid') */
  testIdAttribute?: string;
  /** Default timeout for async utilities in milliseconds (default: 1000) */
  asyncUtilTimeout?: number;
  /** Whether computed styles support pseudo-elements (default: false) */
  computedStyleSupportsPseudoElements?: boolean;
  /** Whether hidden elements should be ignored by default (default: false) */
  defaultHidden?: boolean;
  /** Show original stack trace in error messages (default: false) */
  showOriginalStackTrace?: boolean;
  /** Custom normalizer function for text matching */
  defaultNormalizer?: (text: string) => string;
  /** Ignore specific elements in queries (default: 'script, style') */
  defaultIgnore?: string;
  /** Throw errors on multiple elements found (default: true) */
  throwSuggestions?: boolean;
}

/**
 * Current configuration object
 */
interface Config {
  testIdAttribute: string;
  asyncUtilTimeout: number;
  computedStyleSupportsPseudoElements: boolean;
  defaultHidden: boolean;
  showOriginalStackTrace: boolean;
  defaultNormalizer: (text: string) => string;
  defaultIgnore: string;
  throwSuggestions: boolean;
}

Query Building

Utilities for building custom query functions that follow Testing Library patterns.

/**
 * Build a complete set of query functions from a base queryAllBy function
 * @param queryAllBy - Function that returns all matching elements
 * @param getMultipleError - Function to generate error when multiple elements found
 * @param getMissingError - Function to generate error when no elements found
 * @returns Complete set of query functions (getBy, getAllBy, queryBy, queryAllBy, findBy, findAllBy)
 */
function buildQueries<T extends (...args: any[]) => HTMLElement[]>(
  queryAllBy: T,
  getMultipleError: (container: HTMLElement, ...args: Parameters<T>) => string,
  getMissingError: (container: HTMLElement, ...args: Parameters<T>) => string
): QueryHelpers<T>;

/**
 * Generated query functions from buildQueries
 */
interface QueryHelpers<T extends (...args: any[]) => HTMLElement[]> {
  queryBy: (...args: Parameters<T>) => HTMLElement | null;
  queryAllBy: T;
  getBy: (...args: Parameters<T>) => HTMLElement;
  getAllBy: T;
  findBy: (...args: Parameters<T>) => Promise<HTMLElement>;
  findAllBy: (...args: Parameters<T>) => Promise<HTMLElement[]>;
}

/**
 * Object containing query helper functions
 */
declare const queryHelpers: {
  buildQueries: typeof buildQueries;
  getElementError: (message: string, container: HTMLElement) => Error;
  wrapAllByQueryWithSuggestion: <T extends (...args: any[]) => HTMLElement[]>(
    query: T,
    queryAllByName: string,
    container: HTMLElement
  ) => T;
};

/**
 * Pre-built queries object containing all standard query functions
 */
declare const queries: {
  queryByRole: (container: HTMLElement, role: string, options?: ByRoleOptions) => HTMLElement | null;
  queryAllByRole: (container: HTMLElement, role: string, options?: ByRoleOptions) => HTMLElement[];
  getByRole: (container: HTMLElement, role: string, options?: ByRoleOptions) => HTMLElement;
  getAllByRole: (container: HTMLElement, role: string, options?: ByRoleOptions) => HTMLElement[];
  findByRole: (container: HTMLElement, role: string, options?: ByRoleOptions) => Promise<HTMLElement>;
  findAllByRole: (container: HTMLElement, role: string, options?: ByRoleOptions) => Promise<HTMLElement[]>;
  // ... similar patterns for Text, TestId, LabelText, PlaceholderText, AltText, Title, DisplayValue
};

Text Utilities

Utilities for working with element text content and normalization.

/**
 * Get the text content of a node, including descendant text
 * @param node - Node to get text from
 * @returns Text content of the node
 */
function getNodeText(node: Node): string;

/**
 * Get the default text normalizer function
 * @param options - Normalization options
 * @returns Normalizer function
 */
function getDefaultNormalizer(options?: NormalizerOptions): (text: string) => string;

/**
 * Options for text normalization
 */
interface NormalizerOptions {
  /** Remove leading and trailing whitespace (default: true) */
  trim?: boolean;
  /** Collapse consecutive whitespace to single spaces (default: true) */
  collapseWhitespace?: boolean;
}

Accessibility Utilities

Utilities for working with accessibility features and ARIA roles.

/**
 * Get all available ARIA roles for an element
 * @param element - Element to get roles for
 * @returns Array of available roles
 */
function getRoles(element: HTMLElement): string[];

/**
 * Check if an element is inaccessible (hidden from screen readers)
 * @param element - Element to check
 * @returns True if element is inaccessible
 */
function isInaccessible(element: HTMLElement): boolean;

/**
 * Get suggested query method for finding an element
 * @param element - Element to get suggestion for
 * @param variant - Query variant to suggest (get, query, find)
 * @param method - Specific method preference
 * @returns Suggested query method as string
 */
function getSuggestedQuery(
  element: HTMLElement,
  variant?: 'get' | 'query' | 'find',
  method?: string
): string;

Debugging Utilities

Utilities for debugging and inspecting DOM elements during testing.

/**
 * Pretty-print DOM elements for debugging
 * @param element - Element to pretty-print
 * @param maxLength - Maximum length of output (default: 7000)
 * @param options - Pretty printing options
 * @returns Formatted string representation of element
 */
function prettyDOM(
  element?: Element | HTMLDocument | null,
  maxLength?: number,
  options?: PrettyDOMOptions
): string;

/**
 * Log DOM tree to console
 * @param element - Element to log (defaults to document.body)
 * @param maxLength - Maximum length of output
 * @param options - Pretty printing options
 */
function logDOM(
  element?: Element | HTMLDocument | null,
  maxLength?: number,
  options?: PrettyDOMOptions
): void;

/**
 * Log available ARIA roles to console
 * @param element - Container element (defaults to document.body)
 */
function logRoles(element?: Element | HTMLDocument): void;

/**
 * Options for pretty DOM printing
 */
interface PrettyDOMOptions {
  /** Highlight matching elements */
  highlight?: boolean;
  /** Filter out certain elements */
  filterNode?: (node: Node) => boolean;
}

/**
 * Pretty format values for display (from pretty-format library)
 * @param value - Value to format
 * @param options - Formatting options
 * @returns Formatted string representation
 */
function prettyFormat(value: any, options?: PrettyFormatOptions): string;

/**
 * Options for pretty formatting
 */
interface PrettyFormatOptions {
  /** Maximum depth to traverse (default: Infinity) */
  maxDepth?: number;
  /** Minimum number of elements to trigger multi-line (default: Infinity) */
  min?: boolean;
  /** Include function names (default: false) */
  printFunctionName?: boolean;
}

Error Handling

Utilities for error handling and reporting.

/**
 * Get detailed error message for element queries
 * @param message - Base error message
 * @param container - Container element that was searched
 * @returns Enhanced error object with debugging information
 */
function getElementError(message: string, container: HTMLElement): Error;

Usage Examples

Basic Configuration

import { configure, getConfig } from "@storybook/testing-library";

export const ConfigurationExample = {
  play: async () => {
    // Configure Testing Library
    configure({
      testIdAttribute: 'data-test-id', // Use custom test ID attribute
      asyncUtilTimeout: 2000, // Increase default timeout to 2 seconds
      defaultHidden: true, // Include hidden elements by default
      showOriginalStackTrace: true // Show full stack traces in errors
    });
    
    // Get current configuration
    const config = getConfig();
    console.log('Current timeout:', config.asyncUtilTimeout);
    console.log('Test ID attribute:', config.testIdAttribute);
  }
};

Custom Text Normalizer

import { configure, getDefaultNormalizer } from "@storybook/testing-library";

export const CustomNormalizerExample = {
  play: async () => {
    // Create custom normalizer that removes extra characters
    const customNormalizer = getDefaultNormalizer({
      trim: true,
      collapseWhitespace: true
    });
    
    // Apply custom normalizer globally
    configure({
      defaultNormalizer: (text: string) => {
        // First apply default normalization
        const normalized = customNormalizer(text);
        // Then apply custom logic
        return normalized.replace(/[^\w\s]/gi, ''); // Remove special characters
      }
    });
  }
};

Building Custom Queries

import { buildQueries, within } from "@storybook/testing-library";

// Build custom query for elements with specific CSS class
const queryAllByClass = (container: HTMLElement, className: string) => {
  return Array.from(container.querySelectorAll(`.${className}`));
};

const getMultipleError = (container: HTMLElement, className: string) =>
  `Found multiple elements with class: ${className}`;

const getMissingError = (container: HTMLElement, className: string) =>
  `Unable to find element with class: ${className}`;

const [
  queryByClass,
  getAllByClass,
  getByClass,
  findAllByClass,
  findByClass,
  queryAllByClass
] = buildQueries(queryAllByClass, getMultipleError, getMissingError);

export const CustomQueriesExample = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Use custom queries (need to bind to container manually)
    const errorElement = getByClass(canvasElement, 'error-message');
    const warningElements = getAllByClass(canvasElement, 'warning');
    const optionalElement = queryByClass(canvasElement, 'optional');
    
    expect(errorElement).toBeInTheDocument();
    expect(warningElements).toHaveLength(2);
    expect(optionalElement).toBeNull();
  }
};

Debugging with Pretty DOM

import { within, prettyDOM, logDOM, logRoles } from "@storybook/testing-library";

export const DebuggingExample = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Pretty print specific element
    const form = canvas.getByRole('form');
    console.log('Form HTML:', prettyDOM(form));
    
    // Log entire DOM tree
    logDOM(canvasElement);
    
    // Log available roles
    logRoles(canvasElement);
    
    // Pretty print with options
    const table = canvas.getByRole('table');
    console.log('Table (limited):', prettyDOM(table, 1000, {
      highlight: true,
      filterNode: (node) => node.nodeType === Node.ELEMENT_NODE
    }));
  }
};

Accessibility Utilities

import { within, getRoles, isInaccessible, getSuggestedQuery } from "@storybook/testing-library";

export const AccessibilityExample = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Check available roles
    const button = canvas.getByRole('button');
    const availableRoles = getRoles(button);
    console.log('Available roles for button:', availableRoles);
    
    // Check if element is accessible
    const hiddenElement = canvas.getByTestId('hidden-element');
    const isHidden = isInaccessible(hiddenElement);
    console.log('Element is inaccessible:', isHidden);
    
    // Get suggested query
    const input = canvas.getByLabelText(/username/i);
    const suggestion = getSuggestedQuery(input, 'get');
    console.log('Suggested query:', suggestion);
  }
};

Text Utilities

import { within, getNodeText, getDefaultNormalizer } from "@storybook/testing-library";

export const TextUtilitiesExample = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Get text content including nested elements
    const container = canvas.getByTestId('text-container');
    const fullText = getNodeText(container);
    console.log('Full text content:', fullText);
    
    // Use custom normalizer
    const normalizer = getDefaultNormalizer({
      trim: true,
      collapseWhitespace: true
    });
    
    const rawText = '  Hello    World  \n\n  ';
    const normalizedText = normalizer(rawText);
    console.log('Normalized text:', normalizedText); // "Hello World"
  }
};

Error Handling

import { within, getElementError } from "@storybook/testing-library";

export const ErrorHandlingExample = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    try {
      // This will throw if element not found
      canvas.getByText(/non-existent text/i);
    } catch (error) {
      // Enhance error with debugging info
      const enhancedError = getElementError(
        'Custom error message: Element not found',
        canvasElement
      );
      console.error('Enhanced error:', enhancedError.message);
    }
  }
};

Advanced Configuration

import { configure } from "@storybook/testing-library";

export const AdvancedConfigExample = {
  play: async () => {
    configure({
      // Use custom test ID attribute
      testIdAttribute: 'data-cy',
      
      // Increase timeout for slow operations
      asyncUtilTimeout: 5000,
      
      // Custom text normalizer
      defaultNormalizer: (text: string) => {
        return text
          .toLowerCase()
          .replace(/\s+/g, ' ')
          .trim()
          .replace(/[^\w\s]/g, '');
      },
      
      // Include hidden elements in queries
      defaultHidden: false,
      
      // Show full error stack traces
      showOriginalStackTrace: true,
      
      // Custom ignore pattern
      defaultIgnore: 'script, style, [data-ignore]',
      
      // Disable query suggestions
      throwSuggestions: false
    });
  }
};

Performance Optimization

import { configure, within } from "@storybook/testing-library";

export const PerformanceExample = {
  play: async ({ canvasElement }) => {
    // Configure for better performance
    configure({
      asyncUtilTimeout: 500, // Shorter timeout for fast tests
      computedStyleSupportsPseudoElements: false, // Disable if not needed
      throwSuggestions: false // Reduce error message computation
    });
    
    const canvas = within(canvasElement);
    
    // Use more specific queries for better performance
    const button = canvas.getByRole('button', { name: /exact text/i });
    const input = canvas.getByLabelText(/specific label/i);
    
    expect(button).toBeInTheDocument();
    expect(input).toBeInTheDocument();
  }
};

Install with Tessl CLI

npx tessl i tessl/npm-storybook--testing-library

docs

async-utilities.md

configuration.md

events.md

index.md

queries.md

scoping.md

user-interactions.md

tile.json