import React, { ReactElement } from 'react'; import { render, RenderOptions } from '@testing-library/react-native'; import { DragStateProvider } from '../../contexts/DragStateContext'; import { ListAnimationProvider } from '../../contexts/ListAnimationContext'; import type { DragConfig } from '../../types'; /** * Props for the AllProviders wrapper */ interface AllProvidersProps { children: React.ReactNode; dragConfig?: Partial; entryAnimationTimeout?: number; layoutAnimationDuration?: number; } /** * Wrapper component that provides all required contexts for testing */ const AllProviders: React.FC = ({ children, dragConfig, entryAnimationTimeout, layoutAnimationDuration, }) => { return ( {children} ); }; /** * Custom render options with context configuration */ interface CustomRenderOptions extends Omit { dragConfig?: Partial; entryAnimationTimeout?: number; layoutAnimationDuration?: number; } /** * Custom render function that wraps components with all necessary providers */ const customRender = async ( ui: ReactElement, options?: CustomRenderOptions, ): Promise> => { const { dragConfig, entryAnimationTimeout, layoutAnimationDuration, ...renderOptions } = options ?? {}; const Wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( {children} ); return render(ui, { wrapper: Wrapper, ...renderOptions }); }; // Re-export everything export * from '@testing-library/react-native'; // Override render with our custom version export { customRender as render }; /** * Create a mock list item with required id field */ export interface MockListItem { id: string; title?: string; [key: string]: unknown; } /** * Factory function to create a single mock item */ export const createMockItem = ( id: string, overrides: Partial = {}, ): MockListItem => ({ id, title: `Item ${id}`, ...overrides, }); /** * Factory function to create multiple mock items */ export const createMockItems = (count: number): MockListItem[] => Array.from({ length: count }, (_, i) => createMockItem(`item-${i + 1}`)); /** * Create a mock SharedValue-like object for testing */ export const createMockSharedValue = (initialValue: T) => ({ value: initialValue, addListener: jest.fn(), removeListener: jest.fn(), modify: jest.fn((modifier: (val: T) => T) => modifier(initialValue)), }); /** * Create a mock animated ref for testing */ export const createMockAnimatedRef = (current: T | null = null) => ({ current, }); /** * Wait for animations to complete (mock version) */ export const waitForAnimations = async (ms = 300): Promise => { await new Promise((resolve) => setTimeout(resolve, ms)); }; /** * Mock gesture event data for testing drag operations */ export const createMockGestureEvent = (overrides: Record = {}) => ({ translationX: 0, translationY: 0, velocityX: 0, velocityY: 0, absoluteX: 0, absoluteY: 0, x: 0, y: 0, numberOfPointers: 1, state: 4, // ACTIVE ...overrides, }); /** * Mock FlashList ref for testing */ export const createMockFlashListRef = () => ({ scrollToOffset: jest.fn(), scrollToIndex: jest.fn(), scrollToItem: jest.fn(), scrollToEnd: jest.fn(), getScrollOffset: jest.fn(() => 0), getScrollableNode: jest.fn(() => null), recordInteraction: jest.fn(), flashScrollIndicators: jest.fn(), prepareForLayoutAnimationRender: jest.fn(), }); /** * Drag simulation parameters */ interface DragSimulationParams { from: { x: number; y: number }; to: { x: number; y: number }; duration?: number; steps?: number; } /** * Create a sequence of gesture events to simulate a drag operation. * Returns an array of gesture event objects that can be used to test drag behavior. * * @param params - Drag simulation parameters * @returns Array of gesture events representing the drag sequence */ export const simulateDrag = (params: DragSimulationParams) => { const { from, to, duration = 300, steps = 10 } = params; const events = []; // Calculate deltas per step const deltaX = (to.x - from.x) / steps; const deltaY = (to.y - from.y) / steps; const timeStep = duration / steps; // Start event events.push( createMockGestureEvent({ translationX: 0, translationY: 0, absoluteX: from.x, absoluteY: from.y, x: from.x, y: from.y, state: 2, // BEGAN }), ); // Update events for (let i = 1; i <= steps; i++) { const progress = i / steps; const currentX = from.x + deltaX * i; const currentY = from.y + deltaY * i; events.push( createMockGestureEvent({ translationX: deltaX * i, translationY: deltaY * i, absoluteX: currentX, absoluteY: currentY, x: currentX, y: currentY, state: 4, // ACTIVE time: timeStep * i, }), ); } // End event events.push( createMockGestureEvent({ translationX: to.x - from.x, translationY: to.y - from.y, absoluteX: to.x, absoluteY: to.y, x: to.x, y: to.y, velocityX: (to.x - from.x) / (duration / 1000), velocityY: (to.y - from.y) / (duration / 1000), state: 5, // END }), ); return events; }; /** * Generate a large list of mock items for performance testing. * * @param count - Number of items to generate * @returns Array of mock list items */ export const generateLargeList = (count: number): MockListItem[] => Array.from({ length: count }, (_, i) => ({ id: `item-${i}`, title: `Item ${i}`, index: i, })); /** * Animation timing helper - waits for a specific SharedValue to reach target. * Useful for testing animation completion. * * @param sharedValue - The SharedValue to observe * @param targetValue - The target value to wait for * @param timeout - Maximum wait time in ms (default: 1000) * @returns Promise that resolves when target is reached or rejects on timeout */ export const waitForSharedValue = async ( sharedValue: { value: T }, targetValue: T, timeout = 1000, ): Promise => { const startTime = Date.now(); return new Promise((resolve, reject) => { const checkValue = () => { if (sharedValue.value === targetValue) { resolve(sharedValue.value); return; } if (Date.now() - startTime > timeout) { reject( new Error( `Timeout waiting for SharedValue to reach ${targetValue}. Current value: ${sharedValue.value}`, ), ); return; } setTimeout(checkValue, 16); // Check every frame (~60fps) }; checkValue(); }); }; /** * Performance measurement helper for tests. * Wraps an async function and returns execution time. * * @param fn - Async function to measure * @returns Tuple of [result, executionTimeMs] */ export const measurePerformance = async ( fn: () => Promise, ): Promise<[T, number]> => { const startTime = performance.now(); const result = await fn(); const endTime = performance.now(); return [result, endTime - startTime]; };