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);
});
});
});