import { describe, it, expect, vi, beforeEach } from 'vitest' import { renderHook, waitFor, act } from '@testing-library/react' import { usePhotoUpload } from '@/hooks/usePhotoUpload' import * as api from '@/lib/api' // Mock the API vi.mock('@/lib/api', () => ({ inventoryApi: { uploadItemPhoto: vi.fn(), }, })) describe('usePhotoUpload Hook', () => { beforeEach(() => { vi.clearAllMocks() }) it('should initialize with default state', () => { const { result } = renderHook(() => usePhotoUpload()) expect(result.current.isLoading).toBe(false) expect(result.current.error).toBe(null) }) it('should upload a valid image file', async () => { const mockPhoto = { thumbnail_url: '/images/test_thumb.jpg', full_url: '/images/test_original.jpg', uploaded_at: '2026-04-20T10:00:00Z', } vi.mocked(api.inventoryApi.uploadItemPhoto).mockResolvedValueOnce({ status: 'ok', photo: mockPhoto, }) const { result } = renderHook(() => usePhotoUpload()) const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' }) let photo await act(async () => { photo = await result.current.upload(file, 123) }) expect(photo).toEqual(mockPhoto) expect(result.current.error).toBe(null) }) it('should reject file larger than 10MB', async () => { const { result } = renderHook(() => usePhotoUpload()) // Create a file larger than 10MB const largeFile = new File(['x'.repeat(10 * 1024 * 1024 + 1)], 'large.jpg', { type: 'image/jpeg', }) await act(async () => { try { await result.current.upload(largeFile, 123) } catch (error) { // Expected to throw } }) expect(result.current.error).toBeTruthy() expect(result.current.error).toMatch(/File too large/) }) it('should reject invalid MIME types', async () => { const { result } = renderHook(() => usePhotoUpload()) const invalidFile = new File(['test'], 'test.txt', { type: 'text/plain' }) await act(async () => { try { await result.current.upload(invalidFile, 123) } catch (error) { // Expected to throw } }) expect(result.current.error).toBeTruthy() expect(result.current.error).toMatch(/Invalid image format/) }) it('should set isLoading to true during upload', async () => { let resolveUpload: any const uploadPromise = new Promise((resolve) => { resolveUpload = resolve }) vi.mocked(api.inventoryApi.uploadItemPhoto).mockReturnValueOnce(uploadPromise as any) const { result } = renderHook(() => usePhotoUpload()) const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' }) let uploadPromiseResult await act(async () => { uploadPromiseResult = result.current.upload(file, 123) }) await waitFor(() => { expect(result.current.isLoading).toBe(true) }) await act(async () => { resolveUpload({ status: 'ok', photo: {} }) await uploadPromiseResult }) await waitFor(() => { expect(result.current.isLoading).toBe(false) }) }) it('should accept JPEG, PNG, WebP, and GIF formats', async () => { const formats = [ { type: 'image/jpeg', ext: 'jpg' }, { type: 'image/png', ext: 'png' }, { type: 'image/webp', ext: 'webp' }, { type: 'image/gif', ext: 'gif' }, ] const mockPhoto = { thumbnail_url: '/images/test_thumb.jpg', full_url: '/images/test_original.jpg', uploaded_at: '2026-04-20T10:00:00Z', } vi.mocked(api.inventoryApi.uploadItemPhoto).mockResolvedValue({ status: 'ok', photo: mockPhoto, }) for (const format of formats) { const { result } = renderHook(() => usePhotoUpload()) const file = new File(['test'], `test.${format.ext}`, { type: format.type }) let photo await act(async () => { photo = await result.current.upload(file, 123) }) expect(photo).toEqual(mockPhoto) expect(result.current.error).toBe(null) } }) it('should handle network errors gracefully', async () => { vi.mocked(api.inventoryApi.uploadItemPhoto).mockRejectedValueOnce( new Error('Network error') ) const { result } = renderHook(() => usePhotoUpload()) const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' }) await act(async () => { try { await result.current.upload(file, 123) } catch (error) { // Expected to throw } }) expect(result.current.error).toBeTruthy() expect(result.current.error).toMatch(/Network error/) }) it('should reset error state on successful upload', async () => { const mockPhoto = { thumbnail_url: '/images/test_thumb.jpg', full_url: '/images/test_original.jpg', uploaded_at: '2026-04-20T10:00:00Z', } vi.mocked(api.inventoryApi.uploadItemPhoto).mockResolvedValue({ status: 'ok', photo: mockPhoto, }) const { result } = renderHook(() => usePhotoUpload()) const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' }) await act(async () => { await result.current.upload(file, 123) }) expect(result.current.error).toBe(null) }) it('should return photo object with correct structure', async () => { const mockPhoto = { thumbnail_url: '/images/networking/SFP-LR_thumb.jpg', full_url: '/images/networking/SFP-LR_original.jpg', uploaded_at: '2026-04-20T10:00:00Z', } vi.mocked(api.inventoryApi.uploadItemPhoto).mockResolvedValueOnce({ status: 'ok', photo: mockPhoto, }) const { result } = renderHook(() => usePhotoUpload()) const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' }) let uploadedPhoto await act(async () => { uploadedPhoto = await result.current.upload(file, 123) }) expect(uploadedPhoto).toHaveProperty('thumbnail_url') expect(uploadedPhoto).toHaveProperty('full_url') expect(uploadedPhoto).toHaveProperty('uploaded_at') expect(uploadedPhoto.thumbnail_url).toBe(mockPhoto.thumbnail_url) expect(uploadedPhoto.full_url).toBe(mockPhoto.full_url) }) })