UNPKG

@shopify/shop-minis-react

Version:

React component library for Shopify Shop Minis with Tailwind CSS v4 support (source-only, requires TypeScript)

323 lines (274 loc) 9.06 kB
import {renderHook, act} from '@testing-library/react' import {describe, expect, it, vi, beforeEach} from 'vitest' import {useShopActions} from '../../internal/useShopActions' import {useImageUpload} from './useImageUpload' // Mock dependencies vi.mock('../../internal/useShopActions', () => ({ useShopActions: vi.fn(() => ({ createImageUploadLink: vi.fn(), completeImageUpload: vi.fn(), })), })) vi.mock('../../utils', () => ({ fileToDataUri: vi.fn((file: File) => Promise.resolve(`data:${file.type};base64,mockbase64data`) ), })) // Mock fetch globally global.fetch = vi.fn() describe('useImageUpload', () => { let mockCreateImageUploadLink: ReturnType<typeof vi.fn> let mockCompleteImageUpload: ReturnType<typeof vi.fn> beforeEach(() => { vi.clearAllMocks() // Reset fetch mock with proper blob() implementation ;(global.fetch as any).mockImplementation(async (url: string) => { // For data URI fetch (fileToDataUri result) if (url.startsWith('data:')) { return { blob: async () => new Blob(['test image data'], {type: 'image/jpeg'}), } } // Default for other fetches return { ok: true, text: async () => 'Upload successful', } }) // Set up mock actions with proper implementations mockCreateImageUploadLink = vi.fn().mockResolvedValue({ ok: true, data: { targets: [ { url: 'https://storage.googleapis.com/upload', resourceUrl: 'https://storage.googleapis.com/resource/123', parameters: [ {name: 'key', value: 'test-key'}, {name: 'policy', value: 'test-policy'}, ], }, ], }, }) mockCompleteImageUpload = vi.fn().mockResolvedValue({ ok: true, data: { files: [ { id: 'uploaded-image-id', fileStatus: 'READY', image: { url: 'https://example.com/image.jpg', }, }, ], }, }) // Update the mocks to return our mock actions ;(useShopActions as any).mockReturnValue({ createImageUploadLink: mockCreateImageUploadLink, completeImageUpload: mockCompleteImageUpload, }) }) describe('uploadImage', () => { it('successfully uploads an image', async () => { const {result} = renderHook(() => useImageUpload()) const testFile = new File(['test image'], 'test.jpg', { type: 'image/jpeg', }) let uploadedImages: any await act(async () => { uploadedImages = await result.current.uploadImage(testFile) }) // Verify the upload flow expect(mockCreateImageUploadLink).toHaveBeenCalledWith({ input: [ { mimeType: 'image/jpeg', fileSize: 10, // 'test image'.length }, ], }) // Verify GCS upload expect(global.fetch).toHaveBeenCalledWith( 'https://storage.googleapis.com/upload', expect.objectContaining({ method: 'POST', body: expect.any(FormData), }) ) expect(mockCompleteImageUpload).toHaveBeenCalledWith({ resourceUrls: ['https://storage.googleapis.com/resource/123'], }) expect(uploadedImages).toEqual([ { id: 'uploaded-image-id', imageUrl: 'https://example.com/image.jpg', resourceUrl: 'https://storage.googleapis.com/resource/123', }, ]) }) it('throws error when createImageUploadLink fails', async () => { mockCreateImageUploadLink.mockResolvedValue({ ok: false, error: { message: 'Failed to create upload link', }, }) const {result} = renderHook(() => useImageUpload()) const testFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'}) await expect( act(async () => { await result.current.uploadImage(testFile) }) ).rejects.toThrow('Failed to create upload link') }) it('throws error when GCS upload fails', async () => { // Mock failed fetch for GCS upload ;(global.fetch as any).mockImplementation(async (url: string) => { if (url.startsWith('data:')) { return { blob: async () => new Blob(['test'], {type: 'image/jpeg'}), } } // GCS upload fails return { ok: false, text: async () => 'Upload failed', } }) const {result} = renderHook(() => useImageUpload()) const testFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'}) await expect( act(async () => { await result.current.uploadImage(testFile) }) ).rejects.toThrow('Failed to upload image') }) it('throws error when completeImageUpload fails', async () => { mockCompleteImageUpload.mockResolvedValue({ ok: false, error: { message: 'Failed to complete upload', }, }) const {result} = renderHook(() => useImageUpload()) const testFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'}) await expect( act(async () => { await result.current.uploadImage(testFile) }) ).rejects.toThrow('Failed to complete upload') }) it('polls until image is ready', async () => { // First two calls return PROCESSING, third returns READY mockCompleteImageUpload .mockResolvedValueOnce({ ok: true, data: { files: [ { id: 'uploaded-image-id', fileStatus: 'PROCESSING', }, ], }, }) .mockResolvedValueOnce({ ok: true, data: { files: [ { id: 'uploaded-image-id', fileStatus: 'PROCESSING', }, ], }, }) .mockResolvedValueOnce({ ok: true, data: { files: [ { id: 'uploaded-image-id', fileStatus: 'READY', image: { url: 'https://example.com/processed.jpg', }, }, ], }, }) // Speed up test by mocking setTimeout vi.useFakeTimers() const {result} = renderHook(() => useImageUpload()) const testFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'}) // Start the upload without awaiting let uploadPromise: Promise<any> await act(async () => { uploadPromise = result.current.uploadImage(testFile) }) // Advance timers for polling await act(async () => { await vi.advanceTimersByTimeAsync(2000) }) const uploadedImages = await uploadPromise! expect(mockCompleteImageUpload).toHaveBeenCalledTimes(3) expect(uploadedImages).toEqual([ { id: 'uploaded-image-id', imageUrl: 'https://example.com/processed.jpg', resourceUrl: 'https://storage.googleapis.com/resource/123', }, ]) vi.useRealTimers() }) it('handles file without initial size', async () => { const {result} = renderHook(() => useImageUpload()) // Create a file without size property set const testFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'}) let uploadedImages: any await act(async () => { uploadedImages = await result.current.uploadImage(testFile) }) expect(uploadedImages).toEqual([ { id: 'uploaded-image-id', imageUrl: 'https://example.com/image.jpg', resourceUrl: 'https://storage.googleapis.com/resource/123', }, ]) }) it('includes all form data parameters in GCS upload', async () => { let capturedFormData: FormData | undefined ;(global.fetch as any).mockImplementation( async (url: string, options: any) => { if (url.startsWith('data:')) { return { blob: async () => new Blob(['test'], {type: 'image/jpeg'}), } } // eslint-disable-next-line jest/no-if if (url === 'https://storage.googleapis.com/upload') { capturedFormData = options.body } return { ok: true, text: async () => 'Upload successful', } } ) const {result} = renderHook(() => useImageUpload()) const testFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'}) await act(async () => { await result.current.uploadImage(testFile) }) // Verify FormData contains all expected fields expect(capturedFormData).toBeDefined() expect(capturedFormData?.get('key')).toBe('test-key') expect(capturedFormData?.get('policy')).toBe('test-policy') expect(capturedFormData?.get('file')).toBeDefined() }) }) })