import { describe, it, expect, vi, beforeEach } from 'vitest'; import { renderHook, act, waitFor } from '@testing-library/react'; import { useItemCreate } from '@/hooks/useItemCreate'; import * as api from '@/lib/api'; // Mock api module vi.mock('@/lib/api', () => ({ inventoryApi: { createItem: vi.fn(), uploadItemPhoto: vi.fn(), }, })); describe('useItemCreate Hook - Item Creation Flow Integration', () => { beforeEach(() => { vi.resetAllMocks(); }); it('should initialize with details step and empty form', () => { const { result } = renderHook(() => useItemCreate()); expect(result.current.step).toBe('details'); expect(result.current.formData.name).toBe(''); expect(result.current.formData.category).toBe(''); expect(result.current.formData.item_type).toBe(''); expect(result.current.formData.quantity).toBe(1); expect(result.current.uploadedPhoto).toBeNull(); expect(result.current.error).toBeNull(); }); it('should update form data', () => { const { result } = renderHook(() => useItemCreate()); act(() => { result.current.setFormData({ name: 'Test Item' }); result.current.setFormData({ category: 'Electronics' }); result.current.setFormData({ item_type: 'Component' }); }); expect(result.current.formData.name).toBe('Test Item'); expect(result.current.formData.category).toBe('Electronics'); expect(result.current.formData.item_type).toBe('Component'); }); it('should navigate between steps', async () => { const { result } = renderHook(() => useItemCreate()); expect(result.current.step).toBe('details'); act(() => { result.current.goToStep('photo'); }); expect(result.current.step).toBe('photo'); act(() => { result.current.nextStep(); }); expect(result.current.step).toBe('preview'); act(() => { result.current.prevStep(); }); expect(result.current.step).toBe('photo'); }); it('should create item and return response', async () => { const mockCreateItem = vi.mocked(api.inventoryApi.createItem); mockCreateItem.mockResolvedValueOnce({ id: 123, name: 'Test Item' }); const { result } = renderHook(() => useItemCreate()); act(() => { result.current.setFormData({ name: 'Test Item' }); result.current.setFormData({ category: 'Electronics' }); result.current.setFormData({ item_type: 'Component' }); }); let createdItem; await act(async () => { createdItem = await result.current.submitItem(1); }); expect(createdItem?.id).toBe(123); expect(mockCreateItem).toHaveBeenCalledWith(1, expect.objectContaining({ name: 'Test Item', category: 'Electronics', item_type: 'Component', })); }); it('should validate required fields on submission', async () => { const mockCreateItem = vi.mocked(api.inventoryApi.createItem); const { result } = renderHook(() => useItemCreate()); // Try submitting without required fields (empty name) let submitResult; await act(async () => { submitResult = await result.current.submitItem(1); }); // Should return undefined and set error expect(submitResult).toBeUndefined(); expect(result.current.error).toBe('Item name is required'); expect(mockCreateItem).not.toHaveBeenCalled(); }); it('should handle crop bounds independently', () => { const { result } = renderHook(() => useItemCreate()); expect(result.current.cropBounds).toBeNull(); act(() => { result.current.setCropBounds({ x: 10, y: 10, width: 100, height: 100 }); }); expect(result.current.cropBounds).toEqual({ x: 10, y: 10, width: 100, height: 100 }); act(() => { result.current.setCropBounds(null); }); expect(result.current.cropBounds).toBeNull(); }); it('should reset all state', async () => { const mockCreateItem = vi.mocked(api.inventoryApi.createItem); mockCreateItem.mockResolvedValueOnce({ id: 123 }); const { result } = renderHook(() => useItemCreate()); // Populate form and navigate await act(async () => { result.current.setFormData({ name: 'Test Item' }); result.current.setFormData({ category: 'Electronics' }); result.current.setFormData({ item_type: 'Component' }); result.current.goToStep('photo'); await result.current.submitItem(1); }); // Verify populated state expect(result.current.formData.name).toBe('Test Item'); expect(result.current.formData.category).toBe('Electronics'); // Reset act(() => { result.current.reset(); }); expect(result.current.step).toBe('details'); expect(result.current.formData.name).toBe(''); expect(result.current.formData.category).toBe(''); expect(result.current.uploadedPhoto).toBeNull(); expect(result.current.error).toBeNull(); expect(result.current.photoError).toBeNull(); }); it('should complete full workflow: details -> photo -> preview -> confirm', async () => { const mockCreateItem = vi.mocked(api.inventoryApi.createItem); const mockUploadPhoto = vi.mocked(api.inventoryApi.uploadItemPhoto); mockCreateItem.mockResolvedValueOnce({ id: 456 }); mockUploadPhoto.mockResolvedValueOnce({ status: 'ok', photo: { thumbnail_url: 'http://example.com/thumb.jpg', full_url: 'http://example.com/full.jpg', uploaded_at: '2024-01-01T00:00:00Z', }, }); const { result } = renderHook(() => useItemCreate()); // Step 1: Details expect(result.current.step).toBe('details'); act(() => { result.current.setFormData({ name: 'Workflow Item', category: 'Electronics', item_type: 'Component', quantity: 5, }); }); await act(async () => { await result.current.submitItem(1); }); // Step 2: Photo act(() => { result.current.goToStep('photo'); }); expect(result.current.step).toBe('photo'); // Step 3: Preview act(() => { result.current.goToStep('preview'); result.current.setCropBounds({ x: 0, y: 0, width: 100, height: 100 }); }); expect(result.current.step).toBe('preview'); expect(result.current.cropBounds).not.toBeNull(); // Step 4: Confirm act(() => { result.current.goToStep('confirm'); }); expect(result.current.step).toBe('confirm'); // Reset (simulating completion) act(() => { result.current.reset(); }); expect(result.current.step).toBe('details'); expect(result.current.formData.name).toBe(''); }); it('should call API with correct item data structure', async () => { const mockCreateItem = vi.mocked(api.inventoryApi.createItem); mockCreateItem.mockResolvedValueOnce({ id: 999 }); const { result } = renderHook(() => useItemCreate()); act(() => { result.current.setFormData({ name: 'Precise Item', category: 'Tools', item_type: 'Power Tool', quantity: 10, barcode: 'BAR123', part_number: 'PT-001', }); }); await act(async () => { await result.current.submitItem(5); }); expect(mockCreateItem).toHaveBeenCalledWith(5, expect.objectContaining({ name: 'Precise Item', category: 'Tools', item_type: 'Power Tool', quantity: 10, barcode: 'BAR123', part_number: 'PT-001', })); }); it('should handle API errors gracefully', async () => { const mockCreateItem = vi.mocked(api.inventoryApi.createItem); mockCreateItem.mockRejectedValueOnce(new Error('Network error')); const { result } = renderHook(() => useItemCreate()); act(() => { result.current.setFormData({ name: 'Test Item' }); result.current.setFormData({ category: 'Electronics' }); result.current.setFormData({ item_type: 'Component' }); }); let submitResult; await act(async () => { submitResult = await result.current.submitItem(1); }); // Should return undefined on error expect(submitResult).toBeUndefined(); // Error should be set (may need to wait for state update) await waitFor(() => { expect(result.current.error).toBeTruthy(); }); }); });