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

scoping.mddocs/

Element Scoping

Scoping utilities for limiting queries to specific DOM containers, essential for Storybook story isolation. The within function creates a scoped set of queries bound to a specific container element.

Capabilities

Within Function

Creates a new set of query functions bound to a specific container element, ensuring queries only search within that container.

/**
 * Create scoped queries bound to a specific container element
 * @param element - Container element to scope queries to
 * @returns Object containing all query functions bound to the container
 */
function within(element: HTMLElement): BoundQueries;

/**
 * All query functions bound to a specific container element
 * Essential for Storybook story isolation - use instead of screen object
 */
interface BoundQueries {
  // getBy* queries - throw if not found
  getByRole(role: string, options?: ByRoleOptions): HTMLElement;
  getByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
  getByTestId(testId: string, options?: SelectorMatcherOptions): HTMLElement;
  getByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
  getByPlaceholderText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
  getByAltText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
  getByTitle(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
  getByDisplayValue(value: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
  
  // getAllBy* queries - throw if none found, return array
  getAllByRole(role: string, options?: ByRoleOptions): HTMLElement[];
  getAllByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
  getAllByTestId(testId: string, options?: SelectorMatcherOptions): HTMLElement[];
  getAllByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
  getAllByPlaceholderText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
  getAllByAltText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
  getAllByTitle(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
  getAllByDisplayValue(value: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
  
  // queryBy* queries - return null if not found
  queryByRole(role: string, options?: ByRoleOptions): HTMLElement | null;
  queryByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
  queryByTestId(testId: string, options?: SelectorMatcherOptions): HTMLElement | null;
  queryByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
  queryByPlaceholderText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
  queryByAltText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
  queryByTitle(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
  queryByDisplayValue(value: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
  queryByAttribute(attribute: string, value: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
  
  // queryAllBy* queries - return empty array if none found
  queryAllByRole(role: string, options?: ByRoleOptions): HTMLElement[];
  queryAllByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
  queryAllByTestId(testId: string, options?: SelectorMatcherOptions): HTMLElement[];
  queryAllByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
  queryAllByPlaceholderText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
  queryAllByAltText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
  queryAllByTitle(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
  queryAllByDisplayValue(value: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
  queryAllByAttribute(attribute: string, value: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
  
  // findBy* queries - async, reject if not found within timeout
  findByRole(role: string, options?: ByRoleOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
  findByText(text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
  findByTestId(testId: string, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
  findByLabelText(text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
  findByPlaceholderText(text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
  findByAltText(text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
  findByTitle(text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
  findByDisplayValue(value: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
  
  // findAllBy* queries - async, reject if none found within timeout
  findAllByRole(role: string, options?: ByRoleOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
  findAllByText(text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
  findAllByTestId(testId: string, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
  findAllByLabelText(text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
  findAllByPlaceholderText(text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
  findAllByAltText(text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
  findAllByTitle(text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
  findAllByDisplayValue(value: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
}

Get Queries For Element

Creates bound query functions for a specific element, similar to within but with a different interface.

/**
 * Get all bound query functions for a specific element
 * @param element - Container element to bind queries to
 * @returns Object containing all query functions bound to the element
 */
function getQueriesForElement(element: HTMLElement): BoundQueries;

Usage Examples

Basic Scoping

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

export const ScopingExample = {
  play: async ({ canvasElement }) => {
    // Create scoped queries for the story canvas
    const canvas = within(canvasElement);
    
    // All queries are now scoped to canvasElement
    const button = canvas.getByRole('button', { name: /submit/i });
    const input = canvas.getByLabelText(/email/i);
    const error = canvas.queryByTestId('error-message');
    
    // Queries will only find elements within canvasElement
    const results = canvas.getAllByRole('listitem');
  }
};

Nested Scoping

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

export const NestedScopingExample = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Find a nested container
    const modal = canvas.getByRole('dialog');
    
    // Create scoped queries for the modal
    const modalQueries = within(modal);
    
    // Queries are now scoped to the modal only
    const modalButton = modalQueries.getByRole('button', { name: /close/i });
    const modalInput = modalQueries.getByLabelText(/search/i);
    
    // This will only search within the modal, not the entire canvas
    const modalContent = modalQueries.getByText(/modal content/i);
  }
};

Component Testing

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

export const ComponentTestingExample = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Test a specific component within the story
    const searchComponent = canvas.getByTestId('search-component');
    const searchQueries = within(searchComponent);
    
    // Interact only within the search component
    const searchInput = searchQueries.getByRole('textbox');
    const searchButton = searchQueries.getByRole('button', { name: /search/i });
    
    await userEvent.type(searchInput, 'test query');
    await userEvent.click(searchButton);
    
    // Verify results within the component
    const results = searchQueries.getAllByRole('option');
    expect(results).toHaveLength(3);
  }
};

Form Section Testing

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

export const FormSectionExample = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Test different sections of a form independently
    const personalInfoSection = canvas.getByTestId('personal-info');
    const personalInfo = within(personalInfoSection);
    
    const addressSection = canvas.getByTestId('address-info');  
    const addressInfo = within(addressSection);
    
    // Fill personal info section
    await userEvent.type(personalInfo.getByLabelText(/first name/i), 'John');
    await userEvent.type(personalInfo.getByLabelText(/last name/i), 'Doe');
    
    // Fill address info section
    await userEvent.type(addressInfo.getByLabelText(/street/i), '123 Main St');
    await userEvent.type(addressInfo.getByLabelText(/city/i), 'Anytown');
    
    // Verify each section independently
    expect(personalInfo.getByDisplayValue('John')).toBeInTheDocument();
    expect(addressInfo.getByDisplayValue('123 Main St')).toBeInTheDocument();
  }
};

Table Testing

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

export const TableTestingExample = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Find the table
    const table = canvas.getByRole('table');
    const tableQueries = within(table);
    
    // Test specific rows
    const rows = tableQueries.getAllByRole('row');
    expect(rows).toHaveLength(5); // Header + 4 data rows
    
    // Test a specific row
    const firstDataRow = rows[1]; // Skip header row
    const rowQueries = within(firstDataRow);
    
    // Find cells within the row
    const nameCell = rowQueries.getByRole('cell', { name: /john doe/i });
    const actionButton = rowQueries.getByRole('button', { name: /edit/i });
    
    expect(nameCell).toBeInTheDocument();
    expect(actionButton).toBeEnabled();
  }
};

Screen Object Alternative

// ❌ Deprecated - shows warning in Storybook
import { screen } from "@storybook/testing-library";

export const ScreenExample = {
  play: async () => {
    // This will show a deprecation warning
    const button = screen.getByRole('button');
  }
};

// ✅ Recommended - use within() instead
import { within } from "@storybook/testing-library";

export const WithinExample = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole('button');
  }
};

Advanced Patterns

Dynamic Container Selection

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

export const DynamicContainerExample = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Find container based on current state
    const activeTab = canvas.getByRole('tabpanel', { hidden: false });
    const tabQueries = within(activeTab);
    
    // Work with content in the active tab only
    const tabContent = tabQueries.getByText(/tab content/i);
    const tabButton = tabQueries.getByRole('button');
  }
};

Multi-Container Testing

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

export const MultiContainerExample = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Test interaction between multiple containers
    const sourceList = canvas.getByTestId('source-list');
    const targetList = canvas.getByTestId('target-list');
    
    const sourceQueries = within(sourceList);
    const targetQueries = within(targetList);
    
    // Move item from source to target
    const item = sourceQueries.getByText('Item 1');
    const moveButton = sourceQueries.getByRole('button', { name: /move/i });
    
    await userEvent.click(moveButton);
    
    // Verify item moved to target
    expect(sourceQueries.queryByText('Item 1')).not.toBeInTheDocument();
    expect(targetQueries.getByText('Item 1')).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