import { renderHook, act, waitFor } from '@testing-library/react-native' import { useVideos } from './use-videos' import { root } from '@repo/core' import { ProjectController } from '@repo/sdk' import { handleError } from './use-error' import { OWNER_ID } from '@/lib/auth' 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 } } }), })) jest.mock('@/lib/auth', () => ({ OWNER_ID: 'test-owner-id', })) describe('useVideos', () => { beforeEach(() => { jest.clearAllMocks() }) afterEach(() => { jest.restoreAllMocks() }) describe('initial state', () => { it('should return initial state', () => { const mockProjectController = { list: jest.fn().mockResolvedValue({ projects: [], total: 0, page: 1, limit: 20, }), } ;(root.get as jest.Mock).mockReturnValue(mockProjectController) const { result } = renderHook(() => useVideos()) expect(result.current.loading).toBe(false) expect(result.current.loadingMore).toBe(false) expect(result.current.error).toBeNull() expect(result.current.data).toBeUndefined() expect(result.current.projects).toEqual([]) expect(result.current.hasMore).toBe(true) }) }) describe('execute function', () => { it('should load projects successfully', async () => { const mockData = { projects: [ { id: '1', title: 'Video 1', userId: 'user-1', isDeleted: false, createdAt: new Date(), updatedAt: new Date(), }, { id: '2', title: 'Video 2', userId: 'user-1', isDeleted: false, createdAt: new Date(), updatedAt: new Date(), }, ], total: 2, page: 1, limit: 20, } const mockProjectController = { list: jest.fn().mockResolvedValue(mockData), } ;(root.get as jest.Mock).mockReturnValue(mockProjectController) const { result } = renderHook(() => useVideos()) await act(async () => { await result.current.execute() }) expect(mockProjectController.list).toHaveBeenCalledWith({ page: 1, limit: 20, }) expect(result.current.data).toEqual(mockData) expect(result.current.projects).toEqual(mockData.projects) expect(result.current.error).toBeNull() }) it('should handle API errors', async () => { const mockError = { status: 500, statusText: 'Internal Server Error', message: 'Failed to load projects', } const mockProjectController = { list: jest.fn().mockRejectedValue(mockError), } ;(root.get as jest.Mock).mockReturnValue(mockProjectController) const { result } = renderHook(() => useVideos()) await act(async () => { await result.current.execute() }) expect(result.current.error).toEqual(mockError) expect(result.current.data).toBeUndefined() }) it('should merge custom params with default params', async () => { const mockData = { projects: [], total: 0, page: 2, limit: 10 } const mockProjectController = { list: jest.fn().mockResolvedValue(mockData), } ;(root.get as jest.Mock).mockReturnValue(mockProjectController) const { result } = renderHook(() => useVideos({ limit: 10 })) await act(async () => { await result.current.execute({ page: 2 }) }) expect(mockProjectController.list).toHaveBeenCalledWith({ page: 2, limit: 10, }) }) it('should calculate hasMore correctly', async () => { const mockData = { projects: [], total: 50, page: 1, limit: 20, } const mockProjectController = { list: jest.fn().mockResolvedValue(mockData), } ;(root.get as jest.Mock).mockReturnValue(mockProjectController) const { result } = renderHook(() => useVideos()) await act(async () => { await result.current.execute() }) expect(result.current.hasMore).toBe(true) }) }) describe('loadMore function', () => { it('should load next page and append data', async () => { const mockFirstPage = { projects: [{ id: '1', title: 'Video 1' }], total: 30, page: 1, limit: 20, } const mockSecondPage = { projects: [{ id: '2', title: 'Video 2' }], total: 30, page: 2, limit: 20, } const mockProjectController = { list: jest.fn() .mockResolvedValueOnce(mockFirstPage) .mockResolvedValueOnce(mockSecondPage), } ;(root.get as jest.Mock).mockReturnValue(mockProjectController) const { result } = renderHook(() => useVideos()) await act(async () => { await result.current.execute() }) await act(async () => { await result.current.loadMore() }) expect(mockProjectController.list).toHaveBeenCalledTimes(2) expect(result.current.projects).toHaveLength(2) expect(result.current.projects[0].id).toBe('1') expect(result.current.projects[1].id).toBe('2') }) it('should not load if already loading', async () => { const mockProjectController = { list: jest.fn().mockResolvedValue({ projects: [], total: 0, page: 1, limit: 20 }), } ;(root.get as jest.Mock).mockReturnValue(mockProjectController) const { result } = renderHook(() => useVideos()) await act(async () => { await result.current.execute() }) const firstLoadMore = act(async () => { await result.current.loadMore() }) act(() => { result.current.loadMore() }) await firstLoadMore expect(mockProjectController.list).toHaveBeenCalledTimes(1) }) it('should not load if no more data', async () => { const mockData = { projects: [], total: 20, page: 1, limit: 20, } const mockProjectController = { list: jest.fn().mockResolvedValue(mockData), } ;(root.get as jest.Mock).mockReturnValue(mockProjectController) const { result } = renderHook(() => useVideos()) await act(async () => { await result.current.execute() }) expect(result.current.hasMore).toBe(false) await act(async () => { await result.current.loadMore() }) expect(mockProjectController.list).toHaveBeenCalledTimes(1) }) }) describe('refetch function', () => { it('should reset and reload data', async () => { const mockFirstData = { projects: [{ id: '1', title: 'Video 1' }], total: 50, page: 1, limit: 20, } const mockRefetchData = { projects: [{ id: '2', title: 'Video 2' }], total: 50, page: 1, limit: 20, } const mockProjectController = { list: jest.fn() .mockResolvedValueOnce(mockFirstData) .mockResolvedValueOnce(mockRefetchData), } ;(root.get as jest.Mock).mockReturnValue(mockProjectController) const { result } = renderHook(() => useVideos()) await act(async () => { await result.current.execute() }) expect(result.current.projects[0].id).toBe('1') await act(async () => { await result.current.refetch() }) expect(result.current.projects[0].id).toBe('2') expect(result.current.hasMore).toBe(true) }) }) describe('loading states', () => { it('should set loading to true during execute', async () => { let resolveFetch: (value: any) => void const fetchPromise = new Promise((resolve) => { resolveFetch = resolve }) const mockProjectController = { list: jest.fn().mockReturnValue(fetchPromise), } ;(root.get as jest.Mock).mockReturnValue(mockProjectController) const { result } = renderHook(() => useVideos()) act(() => { result.current.execute() }) expect(result.current.loading).toBe(true) await act(async () => { resolveFetch!({ projects: [], total: 0, page: 1, limit: 20 }) await fetchPromise }) expect(result.current.loading).toBe(false) }) it('should set loadingMore to true during loadMore', async () => { const mockFirstPage = { projects: [], total: 30, page: 1, limit: 20, } let resolveFetch: (value: any) => void const fetchPromise = new Promise((resolve) => { resolveFetch = resolve }) const mockProjectController = { list: jest.fn() .mockResolvedValueOnce(mockFirstPage) .mockReturnValueOnce(fetchPromise), } ;(root.get as jest.Mock).mockReturnValue(mockProjectController) const { result } = renderHook(() => useVideos()) await act(async () => { await result.current.execute() }) act(() => { result.current.loadMore() }) expect(result.current.loadingMore).toBe(true) await act(async () => { resolveFetch!({ projects: [], total: 30, page: 2, limit: 20 }) await fetchPromise }) expect(result.current.loadingMore).toBe(false) }) }) })