import React from 'react'; import { renderHook, act } from '@testing-library/react-native'; import { DragStateProvider, useDragState, } from '../../contexts/DragStateContext'; import { DEFAULT_DRAG_CONFIG } from '../../constants'; import { createMockFlashListRef } from '../utils/test-utils'; describe('DragStateContext', () => { describe('useDragState', () => { it('throws when used outside DragStateProvider', async () => { const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); await expect(renderHook(() => useDragState())).rejects.toThrow( 'useDragState must be used within DragStateProvider', ); consoleSpy.mockRestore(); }); it('provides context value when used within DragStateProvider', async () => { const wrapper = ({ children }: { children: React.ReactNode }) => ( {children} ); const { result } = await renderHook(() => useDragState(), { wrapper }); expect(result.current).toBeDefined(); expect(result.current.isDragging).toBeDefined(); expect(result.current.draggedIndex).toBeDefined(); expect(result.current.draggedItemId).toBeDefined(); }); }); describe('DragStateProvider', () => { const createWrapper = (config?: Parameters[0]['config']) => { const Wrapper = ({ children }: { children: React.ReactNode }) => ( {children} ); return Wrapper; }; it('initializes SharedValues with default values', async () => { const { result } = await renderHook(() => useDragState(), { wrapper: createWrapper(), }); expect(result.current.isDragging.value).toBe(false); expect(result.current.draggedIndex.value).toBe(-1); expect(result.current.draggedItemId.value).toBe(''); expect(result.current.currentTranslateY.value).toBe(0); expect(result.current.draggedScale.value).toBe(1); expect(result.current.scrollOffset.value).toBe(0); expect(result.current.contentHeight.value).toBe(0); expect(result.current.visibleHeight.value).toBe(0); expect(result.current.measuredItemHeight.value).toBe(0); expect(result.current.isDropping.value).toBe(false); }); it('uses default drag config', async () => { const { result } = await renderHook(() => useDragState(), { wrapper: createWrapper(), }); expect(result.current.config).toEqual(DEFAULT_DRAG_CONFIG); }); it('merges custom config with defaults', async () => { const customConfig = { longPressDuration: 500, dragScale: 1.1, }; const { result } = await renderHook(() => useDragState(), { wrapper: createWrapper(customConfig), }); expect(result.current.config.longPressDuration).toBe(500); expect(result.current.config.dragScale).toBe(1.1); // Should still have other default values expect(result.current.config.estimatedItemHeight).toBe( DEFAULT_DRAG_CONFIG.estimatedItemHeight, ); }); it('provides setListRef function', async () => { const { result } = await renderHook(() => useDragState(), { wrapper: createWrapper(), }); expect(typeof result.current.setListRef).toBe('function'); }); it('provides scrollToOffset function', async () => { const { result } = await renderHook(() => useDragState(), { wrapper: createWrapper(), }); expect(typeof result.current.scrollToOffset).toBe('function'); }); it('scrollToOffset calls the ref scrollToOffset when ref is set', async () => { const { result } = await renderHook(() => useDragState(), { wrapper: createWrapper(), }); const mockRef = createMockFlashListRef(); await act(async () => { result.current.setListRef(mockRef as any); }); await act(async () => { result.current.scrollToOffset(100, true); }); expect(mockRef.scrollToOffset).toHaveBeenCalledWith({ offset: 100, animated: true, }); }); it('scrollToOffset does not crash when ref is null', async () => { const { result } = await renderHook(() => useDragState(), { wrapper: createWrapper(), }); // Should not throw when ref is not set await expect( act(async () => { result.current.scrollToOffset(100); }), ).resolves.not.toThrow(); }); it('resetDragState resets all values to defaults', async () => { const { result } = await renderHook(() => useDragState(), { wrapper: createWrapper(), }); // Simulate drag state await act(async () => { result.current.isDragging.value = true; result.current.draggedIndex.value = 2; result.current.draggedItemId.value = 'item-2'; result.current.currentTranslateY.value = 100; result.current.draggedScale.value = 1.05; result.current.measuredItemHeight.value = 80; result.current.isDropping.value = true; }); // Reset await act(async () => { result.current.resetDragState(); }); // Verify reset expect(result.current.isDragging.value).toBe(false); expect(result.current.draggedIndex.value).toBe(-1); expect(result.current.draggedItemId.value).toBe(''); expect(result.current.currentTranslateY.value).toBe(0); expect(result.current.draggedScale.value).toBe(1); expect(result.current.measuredItemHeight.value).toBe(0); expect(result.current.isDropping.value).toBe(false); }); }); });