import { renderHook, act, waitFor } from '@testing-library/react-native' import { useUserFavorites } from './use-user-favorites' import { root } from '@repo/core' import { TemplateSocialController } from '@repo/sdk' import { handleError } from './use-error' jest.mock('@repo/core', () => ({ root: { get: jest.fn(), }, })) jest.mock('./use-error', () => ({ handleError: jest.fn(async (cb) => { try { const data = await cb() return { data, error: null } } catch (e) { return { data: null, error: e } } }), })) describe('useUserFavorites', () => { beforeEach(() => { jest.clearAllMocks() }) afterEach(() => { jest.restoreAllMocks() }) describe('initial state', () => { it('should return initial state with no data', () => { const { result } = renderHook(() => useUserFavorites()) expect(result.current.data).toBeUndefined() expect(result.current.favorites).toEqual([]) expect(result.current.loading).toBe(false) expect(result.current.loadingMore).toBe(false) expect(result.current.error).toBeNull() expect(result.current.hasMore).toBe(true) }) }) describe('execute function', () => { it('should load user favorites successfully', async () => { const mockData = { favorites: [ { id: '1', templateId: 'template-1', createdAt: new Date('2024-01-01'), template: { id: 'template-1', title: 'Template 1', coverImageUrl: 'https://example.com/image1.jpg', previewUrl: 'https://example.com/preview1.jpg', }, }, { id: '2', templateId: 'template-2', createdAt: new Date('2024-01-02'), template: { id: 'template-2', title: 'Template 2', coverImageUrl: 'https://example.com/image2.jpg', previewUrl: 'https://example.com/preview2.jpg', }, }, ], total: 2, page: 1, limit: 20, } const mockController = { getUserFavorites: jest.fn().mockResolvedValue(mockData), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites()) await act(async () => { await result.current.execute() }) expect(mockController.getUserFavorites).toHaveBeenCalledWith({ limit: 20, page: 1, }) expect(result.current.data).toEqual(mockData) expect(result.current.favorites).toEqual(mockData.favorites) expect(result.current.error).toBeNull() expect(result.current.hasMore).toBe(false) }) it('should handle API errors', async () => { const mockError = { status: 500, statusText: 'Internal Server Error', message: 'Failed to load favorites', } const mockController = { getUserFavorites: jest.fn().mockRejectedValue(mockError), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites()) await act(async () => { await result.current.execute() }) expect(result.current.error).toEqual(mockError) expect(result.current.data).toBeUndefined() }) it('should merge custom params with defaults', async () => { const mockData = { favorites: [], total: 0, page: 2, limit: 10, } const mockController = { getUserFavorites: jest.fn().mockResolvedValue(mockData), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites()) await act(async () => { await result.current.execute({ page: 2, limit: 10 }) }) expect(mockController.getUserFavorites).toHaveBeenCalledWith({ limit: 10, page: 2, }) }) it('should use initial params', async () => { const mockData = { favorites: [], total: 0, page: 1, limit: 50, } const mockController = { getUserFavorites: jest.fn().mockResolvedValue(mockData), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites({ limit: 50 })) await act(async () => { await result.current.execute() }) expect(mockController.getUserFavorites).toHaveBeenCalledWith({ limit: 50, page: 1, }) }) it('should support search parameter', async () => { const mockData = { favorites: [], total: 0, page: 1, limit: 20, } const mockController = { getUserFavorites: jest.fn().mockResolvedValue(mockData), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites()) await act(async () => { await result.current.execute({ search: 'test' }) }) expect(mockController.getUserFavorites).toHaveBeenCalledWith({ limit: 20, page: 1, search: 'test', }) }) }) describe('loading state', () => { it('should set loading to true during fetch', async () => { let resolveFetch: (value: any) => void const fetchPromise = new Promise((resolve) => { resolveFetch = resolve }) const mockController = { getUserFavorites: jest.fn().mockReturnValue(fetchPromise), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites()) act(() => { result.current.execute() }) expect(result.current.loading).toBe(true) await act(async () => { resolveFetch!({ favorites: [], total: 0, page: 1, limit: 20 }) await fetchPromise }) expect(result.current.loading).toBe(false) }) it('should set loading to false after error', async () => { const mockError = { status: 500, message: 'Error' } const mockController = { getUserFavorites: jest.fn().mockRejectedValue(mockError), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites()) await act(async () => { await result.current.execute() }) expect(result.current.loading).toBe(false) }) }) describe('pagination - loadMore', () => { it('should load more favorites and append to existing data', async () => { const page1Data = { favorites: [ { id: '1', templateId: 'template-1', createdAt: new Date('2024-01-01'), template: { id: 'template-1', title: 'Template 1', coverImageUrl: 'https://example.com/image1.jpg', previewUrl: 'https://example.com/preview1.jpg', }, }, { id: '2', templateId: 'template-2', createdAt: new Date('2024-01-02'), template: { id: 'template-2', title: 'Template 2', coverImageUrl: 'https://example.com/image2.jpg', previewUrl: 'https://example.com/preview2.jpg', }, }, ], total: 4, page: 1, limit: 2, } const page2Data = { favorites: [ { id: '3', templateId: 'template-3', createdAt: new Date('2024-01-03'), template: { id: 'template-3', title: 'Template 3', coverImageUrl: 'https://example.com/image3.jpg', previewUrl: 'https://example.com/preview3.jpg', }, }, { id: '4', templateId: 'template-4', createdAt: new Date('2024-01-04'), template: { id: 'template-4', title: 'Template 4', coverImageUrl: 'https://example.com/image4.jpg', previewUrl: 'https://example.com/preview4.jpg', }, }, ], total: 4, page: 2, limit: 2, } const mockController = { getUserFavorites: jest.fn() .mockResolvedValueOnce(page1Data) .mockResolvedValueOnce(page2Data), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites({ limit: 2 })) await act(async () => { await result.current.execute() }) expect(result.current.favorites).toHaveLength(2) expect(result.current.hasMore).toBe(true) await act(async () => { await result.current.loadMore() }) expect(result.current.favorites).toHaveLength(4) expect(result.current.favorites).toEqual([ ...page1Data.favorites, ...page2Data.favorites, ]) expect(result.current.hasMore).toBe(false) }) it('should not load more if already loading', async () => { let resolveFetch: (value: any) => void const fetchPromise = new Promise((resolve) => { resolveFetch = resolve }) const mockController = { getUserFavorites: jest.fn().mockReturnValue(fetchPromise), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites()) act(() => { result.current.execute() }) act(() => { result.current.loadMore() }) await act(async () => { resolveFetch!({ favorites: [], total: 0, page: 1, limit: 20 }) await fetchPromise }) expect(mockController.getUserFavorites).toHaveBeenCalledTimes(1) }) it('should not load more if no more data', async () => { const mockData = { favorites: [ { id: '1', templateId: 'template-1', createdAt: new Date('2024-01-01'), template: { id: 'template-1', title: 'Template 1', coverImageUrl: 'https://example.com/image1.jpg', previewUrl: 'https://example.com/preview1.jpg', }, }, ], total: 1, page: 1, limit: 20, } const mockController = { getUserFavorites: jest.fn().mockResolvedValue(mockData), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites()) await act(async () => { await result.current.execute() }) expect(result.current.hasMore).toBe(false) await act(async () => { await result.current.loadMore() }) expect(mockController.getUserFavorites).toHaveBeenCalledTimes(1) }) it('should set loadingMore state correctly', async () => { const page1Data = { favorites: [ { id: '1', templateId: 'template-1', createdAt: new Date('2024-01-01'), template: { id: 'template-1', title: 'Template 1', coverImageUrl: 'https://example.com/image1.jpg', previewUrl: 'https://example.com/preview1.jpg', }, }, ], total: 2, page: 1, limit: 1, } let resolveLoadMore: (value: any) => void const loadMorePromise = new Promise((resolve) => { resolveLoadMore = resolve }) const mockController = { getUserFavorites: jest.fn() .mockResolvedValueOnce(page1Data) .mockReturnValueOnce(loadMorePromise), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites({ limit: 1 })) await act(async () => { await result.current.execute() }) act(() => { result.current.loadMore() }) expect(result.current.loadingMore).toBe(true) await act(async () => { resolveLoadMore!({ favorites: [ { id: '2', templateId: 'template-2', createdAt: new Date('2024-01-02'), template: { id: 'template-2', title: 'Template 2', coverImageUrl: 'https://example.com/image2.jpg', previewUrl: 'https://example.com/preview2.jpg', }, }, ], total: 2, page: 2, limit: 1, }) await loadMorePromise }) expect(result.current.loadingMore).toBe(false) }) }) describe('refetch function', () => { it('should reset and reload data from page 1', async () => { const initialData = { favorites: [ { id: '1', templateId: 'template-1', createdAt: new Date('2024-01-01'), template: { id: 'template-1', title: 'Template 1', coverImageUrl: 'https://example.com/image1.jpg', previewUrl: 'https://example.com/preview1.jpg', }, }, ], total: 1, page: 1, limit: 20, } const refreshedData = { favorites: [ { id: '1', templateId: 'template-1', createdAt: new Date('2024-01-01'), template: { id: 'template-1', title: 'Template 1', coverImageUrl: 'https://example.com/image1.jpg', previewUrl: 'https://example.com/preview1.jpg', }, }, { id: '2', templateId: 'template-2', createdAt: new Date('2024-01-02'), template: { id: 'template-2', title: 'Template 2', coverImageUrl: 'https://example.com/image2.jpg', previewUrl: 'https://example.com/preview2.jpg', }, }, ], total: 2, page: 1, limit: 20, } const mockController = { getUserFavorites: jest.fn() .mockResolvedValueOnce(initialData) .mockResolvedValueOnce(refreshedData), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites()) await act(async () => { await result.current.execute() }) expect(result.current.favorites).toHaveLength(1) await act(async () => { await result.current.refetch() }) expect(result.current.favorites).toHaveLength(2) expect(mockController.getUserFavorites).toHaveBeenCalledTimes(2) }) it('should reset hasMore flag', async () => { const mockData = { favorites: [ { id: '1', templateId: 'template-1', createdAt: new Date('2024-01-01'), template: { id: 'template-1', title: 'Template 1', coverImageUrl: 'https://example.com/image1.jpg', previewUrl: 'https://example.com/preview1.jpg', }, }, ], total: 1, page: 1, limit: 20, } const mockController = { getUserFavorites: jest.fn().mockResolvedValue(mockData), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites()) await act(async () => { await result.current.execute() }) expect(result.current.hasMore).toBe(false) await act(async () => { await result.current.refetch() }) expect(result.current.hasMore).toBe(false) }) }) describe('hasMore flag', () => { it('should set hasMore to true when more pages exist', async () => { const mockData = { favorites: [ { id: '1', templateId: 'template-1', createdAt: new Date('2024-01-01'), template: { id: 'template-1', title: 'Template 1', coverImageUrl: 'https://example.com/image1.jpg', previewUrl: 'https://example.com/preview1.jpg', }, }, ], total: 40, page: 1, limit: 20, } const mockController = { getUserFavorites: jest.fn().mockResolvedValue(mockData), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites()) await act(async () => { await result.current.execute() }) expect(result.current.hasMore).toBe(true) }) it('should set hasMore to false on last page', async () => { const mockData = { favorites: [ { id: '1', templateId: 'template-1', createdAt: new Date('2024-01-01'), template: { id: 'template-1', title: 'Template 1', coverImageUrl: 'https://example.com/image1.jpg', previewUrl: 'https://example.com/preview1.jpg', }, }, ], total: 20, page: 1, limit: 20, } const mockController = { getUserFavorites: jest.fn().mockResolvedValue(mockData), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites()) await act(async () => { await result.current.execute() }) expect(result.current.hasMore).toBe(false) }) }) describe('error handling', () => { it('should clear error on successful execute', async () => { const mockError = { status: 500, message: 'Error' } const mockData = { favorites: [ { id: '1', templateId: 'template-1', createdAt: new Date('2024-01-01'), template: { id: 'template-1', title: 'Template 1', coverImageUrl: 'https://example.com/image1.jpg', previewUrl: 'https://example.com/preview1.jpg', }, }, ], total: 1, page: 1, limit: 20, } const mockController = { getUserFavorites: jest.fn() .mockRejectedValueOnce(mockError) .mockResolvedValueOnce(mockData), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites()) await act(async () => { await result.current.execute() }) expect(result.current.error).toEqual(mockError) await act(async () => { await result.current.execute() }) expect(result.current.error).toBeNull() }) it('should handle loadMore errors without affecting existing data', async () => { const page1Data = { favorites: [ { id: '1', templateId: 'template-1', createdAt: new Date('2024-01-01'), template: { id: 'template-1', title: 'Template 1', coverImageUrl: 'https://example.com/image1.jpg', previewUrl: 'https://example.com/preview1.jpg', }, }, ], total: 2, page: 1, limit: 1, } const mockError = { status: 500, message: 'Error loading more' } const mockController = { getUserFavorites: jest.fn() .mockResolvedValueOnce(page1Data) .mockRejectedValueOnce(mockError), } ;(root.get as jest.Mock).mockReturnValue(mockController) const { result } = renderHook(() => useUserFavorites({ limit: 1 })) await act(async () => { await result.current.execute() }) expect(result.current.favorites).toHaveLength(1) await act(async () => { await result.current.loadMore() }) expect(result.current.favorites).toHaveLength(1) expect(result.current.loadingMore).toBe(false) }) }) })