// src/hooks/useDataExtraction.test.ts import { renderHook, act } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'; import { useDataExtraction } from './useDataExtraction'; import type { Flyer } from '../types'; import { getFlyerBaseUrl } from '../tests/utils/testHelpers'; const FLYER_BASE_URL = getFlyerBaseUrl(); // Create a mock flyer for testing const createMockFlyer = (id: number, storeName: string = `Store ${id}`): Flyer => ({ flyer_id: id, store: { store_id: id, name: storeName, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', }, file_name: `flyer${id}.jpg`, image_url: `${FLYER_BASE_URL}/flyer${id}.jpg`, icon_url: `${FLYER_BASE_URL}/flyer${id}_icon.jpg`, status: 'processed', item_count: 0, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', }); describe('useDataExtraction Hook', () => { let mockOnFlyerUpdate: Mock<(flyer: Flyer) => void>; beforeEach(() => { mockOnFlyerUpdate = vi.fn(); }); describe('Initial State', () => { it('should return handleDataExtracted as a function', () => { const mockFlyer = createMockFlyer(1); const { result } = renderHook(() => useDataExtraction({ selectedFlyer: mockFlyer, onFlyerUpdate: mockOnFlyerUpdate, }), ); expect(typeof result.current.handleDataExtracted).toBe('function'); }); it('should maintain stable function reference across re-renders when dependencies are unchanged', () => { const mockFlyer = createMockFlyer(1); const { result, rerender } = renderHook(() => useDataExtraction({ selectedFlyer: mockFlyer, onFlyerUpdate: mockOnFlyerUpdate, }), ); const initialHandler = result.current.handleDataExtracted; rerender(); expect(result.current.handleDataExtracted).toBe(initialHandler); }); }); describe('Store Name Extraction', () => { it('should update store name when type is store_name', () => { const mockFlyer = createMockFlyer(1, 'Original Store'); const { result } = renderHook(() => useDataExtraction({ selectedFlyer: mockFlyer, onFlyerUpdate: mockOnFlyerUpdate, }), ); act(() => { result.current.handleDataExtracted('store_name', 'New Store Name'); }); expect(mockOnFlyerUpdate).toHaveBeenCalledTimes(1); const updatedFlyer = mockOnFlyerUpdate.mock.calls[0][0]; expect(updatedFlyer.store?.name).toBe('New Store Name'); // Ensure other properties are preserved expect(updatedFlyer.flyer_id).toBe(1); expect(updatedFlyer.image_url).toBe(`${FLYER_BASE_URL}/flyer1.jpg`); }); it('should preserve store_id when updating store name', () => { const mockFlyer = createMockFlyer(42, 'Original Store'); const { result } = renderHook(() => useDataExtraction({ selectedFlyer: mockFlyer, onFlyerUpdate: mockOnFlyerUpdate, }), ); act(() => { result.current.handleDataExtracted('store_name', 'Updated Store'); }); const updatedFlyer = mockOnFlyerUpdate.mock.calls[0][0]; expect(updatedFlyer.store?.store_id).toBe(42); }); }); describe('Date Extraction', () => { it('should call onFlyerUpdate when type is dates', () => { const mockFlyer = createMockFlyer(1); const { result } = renderHook(() => useDataExtraction({ selectedFlyer: mockFlyer, onFlyerUpdate: mockOnFlyerUpdate, }), ); act(() => { result.current.handleDataExtracted('dates', '2024-01-15 - 2024-01-21'); }); // The hook is called but date parsing is not implemented yet // It should still call onFlyerUpdate with the unchanged flyer expect(mockOnFlyerUpdate).toHaveBeenCalledTimes(1); }); }); describe('Null Flyer Handling', () => { it('should not call onFlyerUpdate when selectedFlyer is null', () => { const { result } = renderHook(() => useDataExtraction({ selectedFlyer: null, onFlyerUpdate: mockOnFlyerUpdate, }), ); act(() => { result.current.handleDataExtracted('store_name', 'New Store'); }); expect(mockOnFlyerUpdate).not.toHaveBeenCalled(); }); it('should not throw when selectedFlyer is null', () => { const { result } = renderHook(() => useDataExtraction({ selectedFlyer: null, onFlyerUpdate: mockOnFlyerUpdate, }), ); expect(() => { act(() => { result.current.handleDataExtracted('store_name', 'New Store'); }); }).not.toThrow(); }); }); describe('Callback Stability', () => { it('should update handler when selectedFlyer changes', () => { const mockFlyer1 = createMockFlyer(1, 'Store 1'); const mockFlyer2 = createMockFlyer(2, 'Store 2'); const { result, rerender } = renderHook( ({ selectedFlyer }) => useDataExtraction({ selectedFlyer, onFlyerUpdate: mockOnFlyerUpdate, }), { initialProps: { selectedFlyer: mockFlyer1 } }, ); const handler1 = result.current.handleDataExtracted; rerender({ selectedFlyer: mockFlyer2 }); const handler2 = result.current.handleDataExtracted; // Handler should be different since selectedFlyer changed expect(handler1).not.toBe(handler2); }); it('should update handler when onFlyerUpdate changes', () => { const mockFlyer = createMockFlyer(1); const mockOnFlyerUpdate2: Mock<(flyer: Flyer) => void> = vi.fn(); const { result, rerender } = renderHook( ({ onFlyerUpdate }) => useDataExtraction({ selectedFlyer: mockFlyer, onFlyerUpdate, }), { initialProps: { onFlyerUpdate: mockOnFlyerUpdate } }, ); const handler1 = result.current.handleDataExtracted; rerender({ onFlyerUpdate: mockOnFlyerUpdate2 }); const handler2 = result.current.handleDataExtracted; // Handler should be different since onFlyerUpdate changed expect(handler1).not.toBe(handler2); }); }); });