UNPKG

@shopify/shop-minis-react

Version:

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

266 lines (210 loc) 7.2 kB
import {renderHook} from '@testing-library/react' import {describe, expect, it, vi} from 'vitest' import {useHandleAction} from './useHandleAction' import type {ShopActionResult} from '@shopify/shop-minis-platform/actions' describe('useHandleAction', () => { describe('Success Case', () => { it('returns data when action succeeds', async () => { const mockData = {id: '123', name: 'Test'} const mockAction = vi.fn(() => Promise.resolve({ ok: true as const, data: mockData, } as ShopActionResult<typeof mockData>) ) const {result} = renderHook(() => useHandleAction(mockAction)) const data = await result.current() expect(data).toEqual(mockData) expect(mockAction).toHaveBeenCalledTimes(1) }) it('passes arguments to the action correctly', async () => { const mockData = {success: true} const mockAction = vi.fn((_arg1: string, _arg2: number) => Promise.resolve({ ok: true as const, data: mockData, } as ShopActionResult<typeof mockData>) ) const {result} = renderHook(() => useHandleAction(mockAction)) await result.current('test', 42) expect(mockAction).toHaveBeenCalledWith('test', 42) }) it('handles complex data structures', async () => { const complexData = { user: { id: '1', name: 'John', addresses: [ {street: '123 Main St', city: 'New York'}, {street: '456 Oak Ave', city: 'Boston'}, ], }, metadata: { timestamp: '2024-01-01', version: 2, }, } const mockAction = vi.fn(() => Promise.resolve({ ok: true as const, data: complexData, } as ShopActionResult<typeof complexData>) ) const {result} = renderHook(() => useHandleAction(mockAction)) const data = await result.current() expect(data).toEqual(complexData) }) it('handles null/undefined data', async () => { const mockAction = vi.fn(() => Promise.resolve({ ok: true as const, data: null, } as ShopActionResult<null>) ) const {result} = renderHook(() => useHandleAction(mockAction)) const data = await result.current() expect(data).toBeNull() }) }) describe('Error Case', () => { it('throws error when action fails', async () => { const mockError = { code: 'ERROR_CODE', message: 'Something went wrong', } const mockAction = vi.fn(() => Promise.resolve({ ok: false as const, error: mockError, } as ShopActionResult<any>) ) const {result} = renderHook(() => useHandleAction(mockAction)) await expect(result.current()).rejects.toEqual(mockError) expect(mockAction).toHaveBeenCalledTimes(1) }) it('preserves error structure', async () => { const complexError = { code: 'VALIDATION_ERROR', message: 'Validation failed', details: { fields: ['email', 'password'], reasons: ['Invalid format', 'Too short'], }, } const mockAction = vi.fn(() => Promise.resolve({ ok: false as const, error: complexError, } as unknown as ShopActionResult<any>) ) const {result} = renderHook(() => useHandleAction(mockAction)) await expect(result.current()).rejects.toEqual(complexError) }) it('handles string errors', async () => { const mockAction = vi.fn(() => Promise.resolve({ ok: false as const, error: 'Simple error message', } as unknown as ShopActionResult<any>) ) const {result} = renderHook(() => useHandleAction(mockAction)) await expect(result.current()).rejects.toBe('Simple error message') }) }) describe('Function Stability', () => { it('maintains reference equality across renders', () => { const mockAction = vi.fn(() => Promise.resolve({ ok: true as const, data: 'test', } as ShopActionResult<string>) ) const {result, rerender} = renderHook(() => useHandleAction(mockAction)) const firstRender = result.current rerender() const secondRender = result.current expect(firstRender).toBe(secondRender) }) it('updates when action changes', () => { const mockAction1 = vi.fn(() => Promise.resolve({ ok: true as const, data: 'action1', } as ShopActionResult<string>) ) const mockAction2 = vi.fn(() => Promise.resolve({ ok: true as const, data: 'action2', } as ShopActionResult<string>) ) const {result, rerender} = renderHook( ({action}) => useHandleAction(action), {initialProps: {action: mockAction1}} ) const firstRender = result.current rerender({action: mockAction2}) const secondRender = result.current expect(firstRender).not.toBe(secondRender) }) }) describe('Multiple Calls', () => { it('handles multiple concurrent calls', async () => { let callCount = 0 const mockAction = vi.fn(async () => { const currentCall = ++callCount await new Promise(resolve => setTimeout(resolve, 10)) return { ok: true as const, data: currentCall, } as ShopActionResult<number> }) const {result} = renderHook(() => useHandleAction(mockAction)) const [result1, result2, result3] = await Promise.all([ result.current(), result.current(), result.current(), ]) expect(result1).toBe(1) expect(result2).toBe(2) expect(result3).toBe(3) expect(mockAction).toHaveBeenCalledTimes(3) }) it('handles sequential calls', async () => { let counter = 0 const mockAction = vi.fn( async () => ({ ok: true as const, data: ++counter, }) as ShopActionResult<number> ) const {result} = renderHook(() => useHandleAction(mockAction)) const result1 = await result.current() const result2 = await result.current() const result3 = await result.current() expect(result1).toBe(1) expect(result2).toBe(2) expect(result3).toBe(3) }) }) describe('Promise Behavior', () => { it('returns a promise', () => { const mockAction = vi.fn(() => Promise.resolve({ ok: true as const, data: 'test', } as ShopActionResult<string>) ) const {result} = renderHook(() => useHandleAction(mockAction)) const returnValue = result.current() expect(returnValue).toBeInstanceOf(Promise) }) it('handles rejected promises from action', async () => { const networkError = new Error('Network error') const mockAction = vi.fn(() => Promise.reject(networkError)) const {result} = renderHook(() => useHandleAction(mockAction)) await expect(result.current()).rejects.toThrow('Network error') }) }) })