diff --git a/hooks/use-announcement-actions.test.ts b/hooks/use-announcement-actions.test.ts new file mode 100644 index 0000000..2b2f8fd --- /dev/null +++ b/hooks/use-announcement-actions.test.ts @@ -0,0 +1,97 @@ +import { renderHook, act } from '@testing-library/react-native' +import { useAnnouncementActions } from './use-announcement-actions' +import { root } from '@repo/core' +import { AnnouncementController } from '@repo/sdk' + +jest.mock('@repo/core', () => ({ + root: { + get: jest.fn(), + }, +})) + +describe('useAnnouncementActions', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + describe('initial state', () => { + it('should return initial state', () => { + const mockAnnouncementController = { + markRead: jest.fn(), + } + ;(root.get as jest.Mock).mockReturnValue(mockAnnouncementController) + + const { result } = renderHook(() => useAnnouncementActions()) + + expect(result.current.markReadLoading).toBe(false) + expect(result.current.markReadError).toBeNull() + }) + }) + + describe('markRead', () => { + it('should mark announcement as read successfully', async () => { + const mockAnnouncementController = { + markRead: jest.fn().mockResolvedValue({ message: 'success' }), + } + ;(root.get as jest.Mock).mockReturnValue(mockAnnouncementController) + + const { result } = renderHook(() => useAnnouncementActions()) + + await act(async () => { + await result.current.markRead('announcement-1') + }) + + expect(mockAnnouncementController.markRead).toHaveBeenCalledWith({ id: 'announcement-1' }) + expect(result.current.markReadLoading).toBe(false) + expect(result.current.markReadError).toBeNull() + }) + + it('should handle markRead error', async () => { + const mockError = new Error('Failed to mark as read') + const mockAnnouncementController = { + markRead: jest.fn().mockRejectedValue(mockError), + } + ;(root.get as jest.Mock).mockReturnValue(mockAnnouncementController) + + const { result } = renderHook(() => useAnnouncementActions()) + + await act(async () => { + await result.current.markRead('announcement-1') + }) + + expect(result.current.markReadError).toEqual(mockError) + expect(result.current.markReadLoading).toBe(false) + }) + + it('should set loading state during markRead', async () => { + let resolveFetch: (value: any) => void + const fetchPromise = new Promise((resolve) => { + resolveFetch = resolve + }) + + const mockAnnouncementController = { + markRead: jest.fn().mockReturnValue(fetchPromise), + } + ;(root.get as jest.Mock).mockReturnValue(mockAnnouncementController) + + const { result } = renderHook(() => useAnnouncementActions()) + + act(() => { + result.current.markRead('announcement-1') + }) + + expect(result.current.markReadLoading).toBe(true) + + await act(async () => { + resolveFetch!({ message: 'success' }) + await fetchPromise + }) + + expect(result.current.markReadLoading).toBe(false) + }) + }) +}) diff --git a/hooks/use-announcement-actions.ts b/hooks/use-announcement-actions.ts new file mode 100644 index 0000000..3134158 --- /dev/null +++ b/hooks/use-announcement-actions.ts @@ -0,0 +1,27 @@ +import { root } from '@repo/core' +import { AnnouncementController } from '@repo/sdk' +import { useState } from 'react' + +export const useAnnouncementActions = () => { + const [markReadLoading, setMarkReadLoading] = useState(false) + const [markReadError, setMarkReadError] = useState(null) + + const markRead = async (id: string) => { + setMarkReadLoading(true) + setMarkReadError(null) + try { + const announcementController = root.get(AnnouncementController) + await announcementController.markRead({ id }) + } catch (error) { + setMarkReadError(error as Error) + } finally { + setMarkReadLoading(false) + } + } + + return { + markRead, + markReadLoading, + markReadError, + } +} diff --git a/hooks/use-announcement-unread-count.test.ts b/hooks/use-announcement-unread-count.test.ts new file mode 100644 index 0000000..2b2b50d --- /dev/null +++ b/hooks/use-announcement-unread-count.test.ts @@ -0,0 +1,101 @@ +import { renderHook, act } from '@testing-library/react-native' +import { useAnnouncementUnreadCount } from './use-announcement-unread-count' +import { root } from '@repo/core' +import { AnnouncementController } from '@repo/sdk' + +jest.mock('@repo/core', () => ({ + root: { + get: jest.fn(), + }, +})) + +describe('useAnnouncementUnreadCount', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + describe('initial state', () => { + it('should return initial state', () => { + const mockAnnouncementController = { + getUnreadCount: jest.fn(), + } + ;(root.get as jest.Mock).mockReturnValue(mockAnnouncementController) + + const { result } = renderHook(() => useAnnouncementUnreadCount()) + + expect(result.current.data).toBeUndefined() + expect(result.current.loading).toBe(false) + expect(result.current.error).toBeNull() + }) + }) + + describe('refetch', () => { + it('should fetch unread count successfully', async () => { + const mockData = { count: 5 } + const mockAnnouncementController = { + getUnreadCount: jest.fn().mockResolvedValue(mockData), + } + ;(root.get as jest.Mock).mockReturnValue(mockAnnouncementController) + + const { result } = renderHook(() => useAnnouncementUnreadCount()) + + await act(async () => { + await result.current.refetch() + }) + + expect(mockAnnouncementController.getUnreadCount).toHaveBeenCalled() + expect(result.current.data).toEqual(mockData) + expect(result.current.loading).toBe(false) + expect(result.current.error).toBeNull() + }) + + it('should handle fetch error', async () => { + const mockError = new Error('Failed to fetch unread count') + const mockAnnouncementController = { + getUnreadCount: jest.fn().mockRejectedValue(mockError), + } + ;(root.get as jest.Mock).mockReturnValue(mockAnnouncementController) + + const { result } = renderHook(() => useAnnouncementUnreadCount()) + + await act(async () => { + await result.current.refetch() + }) + + expect(result.current.error).toEqual(mockError) + expect(result.current.data).toBeUndefined() + expect(result.current.loading).toBe(false) + }) + + it('should set loading state during fetch', async () => { + let resolveFetch: (value: any) => void + const fetchPromise = new Promise((resolve) => { + resolveFetch = resolve + }) + + const mockAnnouncementController = { + getUnreadCount: jest.fn().mockReturnValue(fetchPromise), + } + ;(root.get as jest.Mock).mockReturnValue(mockAnnouncementController) + + const { result } = renderHook(() => useAnnouncementUnreadCount()) + + act(() => { + result.current.refetch() + }) + + expect(result.current.loading).toBe(true) + + await act(async () => { + resolveFetch!({ count: 3 }) + await fetchPromise + }) + + expect(result.current.loading).toBe(false) + }) + }) +}) diff --git a/hooks/use-announcement-unread-count.ts b/hooks/use-announcement-unread-count.ts new file mode 100644 index 0000000..b07e407 --- /dev/null +++ b/hooks/use-announcement-unread-count.ts @@ -0,0 +1,30 @@ +import { root } from '@repo/core' +import { AnnouncementController, type UnreadAnnouncementCountResult } from '@repo/sdk' +import { useState } from 'react' + +export const useAnnouncementUnreadCount = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [data, setData] = useState() + + const refetch = async () => { + setLoading(true) + setError(null) + try { + const announcementController = root.get(AnnouncementController) + const result = await announcementController.getUnreadCount() + setData(result) + } catch (err) { + setError(err as Error) + } finally { + setLoading(false) + } + } + + return { + data, + loading, + error, + refetch, + } +}