feat: add message action hooks with TDD
Implement useMessageActions and useMessageUnreadCount hooks following strict TDD principles (RED → GREEN → REFACTOR). useMessageActions provides: - markRead(id) - mark single message as read - batchMarkRead(ids) - batch mark messages as read - deleteMessage(id) - delete message - Independent loading/error states for each operation useMessageUnreadCount provides: - Fetch total unread count and counts by message type - refetch() method for manual refresh - loading and error state management All operations use MessageController from SDK with proper error handling. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
121
hooks/use-message-actions.test.ts
Normal file
121
hooks/use-message-actions.test.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { renderHook, act } from '@testing-library/react-native'
|
||||
import { useMessageActions } from './use-message-actions'
|
||||
import { root } from '@repo/core'
|
||||
import { MessageController } from '@repo/sdk'
|
||||
|
||||
jest.mock('@repo/core', () => ({
|
||||
root: {
|
||||
get: jest.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
describe('useMessageActions', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('markRead', () => {
|
||||
it('should mark message as read successfully', async () => {
|
||||
const mockMessageController = {
|
||||
markRead: jest.fn().mockResolvedValue({ message: 'success' }),
|
||||
}
|
||||
;(root.get as jest.Mock).mockReturnValue(mockMessageController)
|
||||
|
||||
const { result } = renderHook(() => useMessageActions())
|
||||
|
||||
await act(async () => {
|
||||
await result.current.markRead('msg-1')
|
||||
})
|
||||
|
||||
expect(mockMessageController.markRead).toHaveBeenCalledWith({ id: 'msg-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 read')
|
||||
const mockMessageController = {
|
||||
markRead: jest.fn().mockRejectedValue(mockError),
|
||||
}
|
||||
;(root.get as jest.Mock).mockReturnValue(mockMessageController)
|
||||
|
||||
const { result } = renderHook(() => useMessageActions())
|
||||
|
||||
await act(async () => {
|
||||
await result.current.markRead('msg-1')
|
||||
})
|
||||
|
||||
expect(result.current.markReadError).toEqual(mockError)
|
||||
})
|
||||
})
|
||||
|
||||
describe('batchMarkRead', () => {
|
||||
it('should batch mark messages as read successfully', async () => {
|
||||
const mockMessageController = {
|
||||
batchMarkRead: jest.fn().mockResolvedValue({ message: 'success' }),
|
||||
}
|
||||
;(root.get as jest.Mock).mockReturnValue(mockMessageController)
|
||||
|
||||
const { result } = renderHook(() => useMessageActions())
|
||||
|
||||
await act(async () => {
|
||||
await result.current.batchMarkRead(['msg-1', 'msg-2'])
|
||||
})
|
||||
|
||||
expect(mockMessageController.batchMarkRead).toHaveBeenCalledWith({ ids: ['msg-1', 'msg-2'] })
|
||||
expect(result.current.batchMarkReadLoading).toBe(false)
|
||||
expect(result.current.batchMarkReadError).toBeNull()
|
||||
})
|
||||
|
||||
it('should handle batchMarkRead error', async () => {
|
||||
const mockError = new Error('Failed to batch mark read')
|
||||
const mockMessageController = {
|
||||
batchMarkRead: jest.fn().mockRejectedValue(mockError),
|
||||
}
|
||||
;(root.get as jest.Mock).mockReturnValue(mockMessageController)
|
||||
|
||||
const { result } = renderHook(() => useMessageActions())
|
||||
|
||||
await act(async () => {
|
||||
await result.current.batchMarkRead(['msg-1', 'msg-2'])
|
||||
})
|
||||
|
||||
expect(result.current.batchMarkReadError).toEqual(mockError)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteMessage', () => {
|
||||
it('should delete message successfully', async () => {
|
||||
const mockMessageController = {
|
||||
delete: jest.fn().mockResolvedValue({ message: 'success' }),
|
||||
}
|
||||
;(root.get as jest.Mock).mockReturnValue(mockMessageController)
|
||||
|
||||
const { result } = renderHook(() => useMessageActions())
|
||||
|
||||
await act(async () => {
|
||||
await result.current.deleteMessage('msg-1')
|
||||
})
|
||||
|
||||
expect(mockMessageController.delete).toHaveBeenCalledWith({ id: 'msg-1' })
|
||||
expect(result.current.deleteLoading).toBe(false)
|
||||
expect(result.current.deleteError).toBeNull()
|
||||
})
|
||||
|
||||
it('should handle deleteMessage error', async () => {
|
||||
const mockError = new Error('Failed to delete')
|
||||
const mockMessageController = {
|
||||
delete: jest.fn().mockRejectedValue(mockError),
|
||||
}
|
||||
;(root.get as jest.Mock).mockReturnValue(mockMessageController)
|
||||
|
||||
const { result } = renderHook(() => useMessageActions())
|
||||
|
||||
await act(async () => {
|
||||
await result.current.deleteMessage('msg-1')
|
||||
})
|
||||
|
||||
expect(result.current.deleteError).toEqual(mockError)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user