CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-testing-library--react-native

Simple and complete React Native testing utilities that encourage good testing practices

Pending
Overview
Eval results
Files

interactions.mddocs/

User Interactions

Realistic user interaction simulation supporting press events, text input, scrolling, and other React Native-specific gestures. Provides both synchronous fireEvent API and more realistic asynchronous userEvent API.

Capabilities

FireEvent API

Synchronous event firing utility for immediate event simulation - faster but less realistic than userEvent.

/**
 * Synchronous event firing utility
 * Fires events immediately without realistic timing or validation
 */
declare const fireEvent: {
  /** Fire press event on pressable elements */
  press(element: ReactTestInstance): void;
  
  /** Fire long press event */
  longPress(element: ReactTestInstance): void;
  
  /** Fire text change event on TextInput elements */
  changeText(element: ReactTestInstance, text: string): void;
  
  /** Fire scroll event on scrollable elements */
  scroll(element: ReactTestInstance, eventData?: ScrollEventData): void;
  
  /** Fire focus event on focusable elements */
  focus(element: ReactTestInstance): void;
  
  /** Fire blur event on focusable elements */
  blur(element: ReactTestInstance): void;
  
  /** Fire layout event */
  layout(element: ReactTestInstance, layoutData: LayoutEventData): void;
  
  /** Fire content size change event */
  contentSizeChange(element: ReactTestInstance, contentSize: ContentSizeData): void;
  
  /** Fire end editing event on TextInput */
  endEditing(element: ReactTestInstance, text?: string): void;
  
  /** Fire selection change event on TextInput */
  selectionChange(element: ReactTestInstance, selection: SelectionData): void;
  
  /** Fire refresh event on RefreshControl */
  refresh(element: ReactTestInstance): void;
  
  /** Fire momentum scroll begin event */
  momentumScrollBegin(element: ReactTestInstance): void;
  
  /** Fire momentum scroll end event */
  momentumScrollEnd(element: ReactTestInstance): void;
  
  /** Fire scroll begin drag event */
  scrollBeginDrag(element: ReactTestInstance): void;
  
  /** Fire scroll end drag event */
  scrollEndDrag(element: ReactTestInstance): void;
};

/**
 * Async version of fireEvent for more realistic timing
 */
declare const fireEventAsync: typeof fireEvent;

interface ScrollEventData {
  nativeEvent: {
    contentOffset: { x: number; y: number };
    contentSize: { width: number; height: number };
    layoutMeasurement: { width: number; height: number };
  };
}

interface LayoutEventData {
  nativeEvent: {
    layout: { x: number; y: number; width: number; height: number };
  };
}

interface ContentSizeData {
  nativeEvent: {
    contentSize: { width: number; height: number };
  };
}

interface SelectionData {
  nativeEvent: {
    selection: { start: number; end: number };
  };
}

Usage Examples:

import { render, screen, fireEvent } from "@testing-library/react-native";

test("fireEvent interactions", () => {
  const handlePress = jest.fn();
  const handleTextChange = jest.fn();
  const handleScroll = jest.fn();

  render(
    <View>
      <Pressable onPress={handlePress} testID="button">
        <Text>Press Me</Text>
      </Pressable>
      <TextInput 
        onChangeText={handleTextChange}
        testID="input"
        placeholder="Type here"
      />
      <ScrollView onScroll={handleScroll} testID="scroll">
        <Text>Scrollable content</Text>
      </ScrollView>
    </View>
  );

  // Press button
  const button = screen.getByTestId("button");
  fireEvent.press(button);
  expect(handlePress).toHaveBeenCalledTimes(1);

  // Change text
  const input = screen.getByTestId("input");
  fireEvent.changeText(input, "Hello World");
  expect(handleTextChange).toHaveBeenCalledWith("Hello World");

  // Scroll
  const scrollView = screen.getByTestId("scroll");
  fireEvent.scroll(scrollView, {
    nativeEvent: {
      contentOffset: { x: 0, y: 100 },
      contentSize: { width: 300, height: 1000 },
      layoutMeasurement: { width: 300, height: 400 }
    }
  });
  expect(handleScroll).toHaveBeenCalled();

  // Long press
  fireEvent.longPress(button);

  // Focus and blur
  fireEvent.focus(input);
  fireEvent.blur(input);

  // End editing
  fireEvent.endEditing(input, "Final text");
});

UserEvent API

More realistic user interaction simulation with proper timing, validation, and event sequences.

/**
 * Realistic user event simulation
 * Provides more authentic user interactions with proper timing and validation
 */
declare const userEvent: {
  /** Create configured user event instance */
  setup(config?: UserEventConfig): UserEventInstance;
  
  // Direct access methods for v13 compatibility
  /** Press an element */
  press(element: ReactTestInstance): Promise<void>;
  
  /** Long press an element with optional configuration */
  longPress(element: ReactTestInstance, options?: PressOptions): Promise<void>;
  
  /** Type text into an input element */
  type(element: ReactTestInstance, text: string, options?: TypeOptions): Promise<void>;
  
  /** Clear text from an input element */
  clear(element: ReactTestInstance): Promise<void>;
  
  /** Paste text into an input element */
  paste(element: ReactTestInstance, text: string): Promise<void>;
  
  /** Scroll an element to specific position */
  scrollTo(element: ReactTestInstance, options: ScrollToOptions): Promise<void>;
};

interface UserEventConfig {
  /** Delay between keystrokes when typing (ms) */
  delay?: number;
  
  /** Whether to skip validation of element state */
  skipValidation?: boolean;
  
  /** Custom press duration for long press (ms) */
  longPressDelay?: number;
}

interface UserEventInstance {
  /** Press an element */
  press(element: ReactTestInstance): Promise<void>;
  
  /** Long press with options */
  longPress(element: ReactTestInstance, options?: PressOptions): Promise<void>;
  
  /** Type text with realistic timing */
  type(element: ReactTestInstance, text: string, options?: TypeOptions): Promise<void>;
  
  /** Clear text input */
  clear(element: ReactTestInstance): Promise<void>;
  
  /** Paste text */
  paste(element: ReactTestInstance, text: string): Promise<void>;
  
  /** Scroll to position */
  scrollTo(element: ReactTestInstance, options: ScrollToOptions): Promise<void>;
}

Usage Examples:

import { render, screen, userEvent } from "@testing-library/react-native";

test("userEvent interactions", async () => {
  const user = userEvent.setup({ delay: 10 });
  
  const handlePress = jest.fn();
  const handleTextChange = jest.fn();

  render(
    <View>
      <Pressable onPress={handlePress} testID="button">
        <Text>Press Me</Text>
      </Pressable>
      <TextInput 
        onChangeText={handleTextChange}
        testID="input"
        placeholder="Type here"
      />
    </View>
  );

  // Realistic button press
  const button = screen.getByTestId("button");
  await user.press(button);
  expect(handlePress).toHaveBeenCalledTimes(1);

  // Realistic text typing with delays
  const input = screen.getByTestId("input");
  await user.type(input, "Hello World");
  expect(input.props.value).toBe("Hello World");

  // Clear input
  await user.clear(input);
  expect(input.props.value).toBe("");

  // Paste text
  await user.paste(input, "Pasted content");
  expect(input.props.value).toBe("Pasted content");

  // Long press with custom duration
  await user.longPress(button, { duration: 1000 });
});

test("direct userEvent methods", async () => {
  render(<Pressable testID="button"><Text>Press</Text></Pressable>);
  
  // Direct method usage (v13 compatibility)
  const button = screen.getByTestId("button");
  await userEvent.press(button);
  await userEvent.longPress(button);
});

Press Interactions

Detailed press interaction options and configurations.

/**
 * Press an element with realistic touch behavior
 * @param element - Element to press
 * @returns Promise that resolves when press is complete
 */
function press(element: ReactTestInstance): Promise<void>;

/**
 * Long press an element with configurable duration
 * @param element - Element to long press
 * @param options - Long press configuration options
 * @returns Promise that resolves when long press is complete
 */
function longPress(element: ReactTestInstance, options?: PressOptions): Promise<void>;

interface PressOptions {
  /** Duration of long press in milliseconds (default: 500) */
  duration?: number;
}

Usage Examples:

test("press interactions", async () => {
  const user = userEvent.setup();
  const handlePress = jest.fn();
  const handleLongPress = jest.fn();

  render(
    <Pressable 
      onPress={handlePress}
      onLongPress={handleLongPress}
      testID="interactive-button"
    >
      <Text>Interactive Button</Text>
    </Pressable>
  );

  const button = screen.getByTestId("interactive-button");

  // Standard press
  await user.press(button);
  expect(handlePress).toHaveBeenCalledTimes(1);

  // Long press with default duration (500ms)
  await user.longPress(button);
  expect(handleLongPress).toHaveBeenCalledTimes(1);

  // Long press with custom duration
  await user.longPress(button, { duration: 1000 });
  expect(handleLongPress).toHaveBeenCalledTimes(2);
});

Text Input Interactions

Comprehensive text input simulation with realistic typing, clearing, and pasting.

/**
 * Type text into an input element with realistic timing
 * @param element - TextInput element to type into
 * @param text - Text to type
 * @param options - Typing configuration options
 * @returns Promise that resolves when typing is complete
 */
function type(element: ReactTestInstance, text: string, options?: TypeOptions): Promise<void>;

/**
 * Clear all text from an input element
 * @param element - TextInput element to clear
 * @returns Promise that resolves when clearing is complete
 */
function clear(element: ReactTestInstance): Promise<void>;

/**
 * Paste text into an input element
 * @param element - TextInput element to paste into
 * @param text - Text to paste
 * @returns Promise that resolves when pasting is complete
 */
function paste(element: ReactTestInstance, text: string): Promise<void>;

interface TypeOptions {
  /** Delay between keystrokes in milliseconds */
  delay?: number;
  
  /** Whether to skip element validation */
  skipValidation?: boolean;
  
  /** Whether to clear existing text before typing */
  clearFirst?: boolean;
}

Usage Examples:

test("text input interactions", async () => {
  const user = userEvent.setup();
  const handleTextChange = jest.fn();
  const handleSelectionChange = jest.fn();

  render(
    <TextInput
      testID="text-input"
      placeholder="Enter text"
      onChangeText={handleTextChange}
      onSelectionChange={handleSelectionChange}
      multiline
    />
  );

  const input = screen.getByTestId("text-input");

  // Type with default timing
  await user.type(input, "Hello");
  expect(handleTextChange).toHaveBeenLastCalledWith("Hello");

  // Type with custom delay
  await user.type(input, " World", { delay: 50 });
  expect(handleTextChange).toHaveBeenLastCalledWith("Hello World");

  // Clear input
  await user.clear(input);  
  expect(handleTextChange).toHaveBeenLastCalledWith("");

  // Type after clearing
  await user.type(input, "New text", { clearFirst: true });
  expect(handleTextChange).toHaveBeenLastCalledWith("New text");

  // Paste text
  await user.paste(input, "\nPasted line");
  expect(handleTextChange).toHaveBeenLastCalledWith("New text\nPasted line");
});

test("text input with validation", async () => {
  const user = userEvent.setup();

  render(
    <TextInput
      testID="disabled-input"
      editable={false}
      placeholder="Disabled input"
    />
  );

  const disabledInput = screen.getByTestId("disabled-input");

  // This will throw an error because input is not editable
  await expect(user.type(disabledInput, "text")).rejects.toThrow();

  // Skip validation to force typing
  await user.type(disabledInput, "forced text", { skipValidation: true });
});

Scroll Interactions

Realistic scrolling simulation for ScrollView and other scrollable components.

/**
 * Scroll an element to a specific position
 * @param element - ScrollView or scrollable element
 * @param options - Scroll target options
 * @returns Promise that resolves when scrolling is complete
 */
function scrollTo(element: ReactTestInstance, options: ScrollToOptions): Promise<void>;

interface ScrollToOptions {
  /** Horizontal scroll position */
  x?: number;
  
  /** Vertical scroll position */
  y?: number;
  
  /** Whether to animate the scroll */
  animated?: boolean;
}

Usage Examples:

test("scroll interactions", async () => {
  const user = userEvent.setup();
  const handleScroll = jest.fn();
  const handleMomentumScrollEnd = jest.fn();

  render(
    <ScrollView
      testID="scroll-view"
      onScroll={handleScroll}
      onMomentumScrollEnd={handleMomentumScrollEnd}
      scrollEventThrottle={16}
    >
      <View style={{ height: 2000 }}>
        <Text>Long scrollable content</Text>
      </View>
    </ScrollView>
  );

  const scrollView = screen.getByTestId("scroll-view");

  // Scroll vertically
  await user.scrollTo(scrollView, { y: 500 });
  expect(handleScroll).toHaveBeenCalled();

  // Scroll horizontally  
  await user.scrollTo(scrollView, { x: 200 });

  // Scroll to specific position with animation
  await user.scrollTo(scrollView, { 
    x: 100, 
    y: 300, 
    animated: true 
  });

  // Scroll back to top
  await user.scrollTo(scrollView, { x: 0, y: 0 });
});

Event Validation and Utilities

Utilities for validating element states and event capabilities.

/**
 * Check if element is a touch responder
 * @param element - Element to check
 * @returns True if element can respond to touch events
 */
function isTouchResponder(element: ReactTestInstance): boolean;

/**
 * Check if event is enabled for element
 * @param element - Element to check
 * @param eventName - Event name to validate
 * @returns True if event can be fired on element
 */
function isEventEnabled(
  element: ReactTestInstance, 
  eventName: string
): boolean;

Usage Examples:

import { render, screen, isTouchResponder, isEventEnabled } from "@testing-library/react-native";

test("event validation utilities", () => {
  render(
    <View>
      <Pressable testID="pressable">
        <Text>Pressable</Text>
      </Pressable>
      <View testID="plain-view">
        <Text>Plain View</Text>
      </View>
      <TextInput testID="input" editable={false} />
      <View pointerEvents="none" testID="no-pointer-events">
        <Text>No Pointer Events</Text>
      </View>
    </View>
  );

  const pressable = screen.getByTestId("pressable");
  const plainView = screen.getByTestId("plain-view");
  const input = screen.getByTestId("input");
  const noPointerView = screen.getByTestId("no-pointer-events");

  // Check touch responder capability
  expect(isTouchResponder(pressable)).toBe(true);
  expect(isTouchResponder(plainView)).toBe(false);
  expect(isTouchResponder(input)).toBe(true); // TextInput is always touch responder

  // Check specific event capabilities
  expect(isEventEnabled(pressable, "press")).toBe(true);
  expect(isEventEnabled(noPointerView, "press")).toBe(false);
  expect(isEventEnabled(input, "changeText")).toBe(false); // Not editable
});

Configuration and Setup

Global configuration for user event behavior and timing.

/**
 * Create configured user event instance
 * @param config - User event configuration options
 * @returns Configured user event instance
 */
function setup(config?: UserEventConfig): UserEventInstance;

interface UserEventConfig {
  /** Default delay between keystrokes when typing (ms) */
  delay?: number;
  
  /** Whether to skip element state validation by default */
  skipValidation?: boolean;
  
  /** Default duration for long press interactions (ms) */
  longPressDelay?: number;
  
  /** Whether to advance timers automatically in fake timer environments */
  advanceTimers?: boolean;
}

Usage Examples:

test("user event configuration", async () => {
  // Create instance with custom configuration
  const fastUser = userEvent.setup({ 
    delay: 5,           // Fast typing
    longPressDelay: 200 // Short long press
  });
  
  const slowUser = userEvent.setup({ 
    delay: 100,         // Slow typing
    longPressDelay: 1000 // Long press
  });
  
  const permissiveUser = userEvent.setup({
    skipValidation: true // Skip all validation
  });

  render(<TextInput testID="input" placeholder="Type here" />);
  const input = screen.getByTestId("input");

  // Fast typing
  await fastUser.type(input, "Fast text");
  
  // Slow typing  
  await slowUser.type(input, "Slow text", { clearFirst: true });
  
  // Type in disabled input (normally would throw)
  render(<TextInput testID="disabled" editable={false} />);
  const disabled = screen.getByTestId("disabled");
  await permissiveUser.type(disabled, "Force type");
});

Install with Tessl CLI

npx tessl i tessl/npm-testing-library--react-native

docs

async-testing.md

configuration.md

hooks.md

index.md

interactions.md

matchers.md

queries.md

rendering.md

tile.json