import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { renderHook, waitFor } from '@testing-library/react-native'; import React, { type PropsWithChildren } from 'react'; import { useConversations, useCreateConversation, useDeleteConversation, } from '@/features/conversation/hooks'; import { conversationKeys } from '@/features/conversation/query-keys'; // ─── Mocks ─── const mockList = jest.fn(); const mockCreate = jest.fn(); const mockDelete = jest.fn(); jest.mock('@/features/conversation/api', () => ({ conversationApi: { list: () => mockList(), create: () => mockCreate(), delete: (id: string) => mockDelete(id), detail: jest.fn(), end: jest.fn(), messages: jest.fn(), organize: jest.fn(), }, })); // ─── Helpers ─── let queryClient: QueryClient; function createWrapper() { queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: Infinity }, mutations: { retry: false }, }, }); return function Wrapper({ children }: PropsWithChildren) { return ( {children} ); }; } afterEach(async () => { await queryClient?.cancelQueries(); queryClient?.clear(); jest.clearAllMocks(); }); const fakeConversations = [ { id: 'c1', title: '第一次对话', avatarUrl: null, latestMessagePreview: '你好', latestMessageTime: 1000, startedAt: 1000, unreadCount: 0, isDefaultAssistant: false, hasUserMessage: true, }, ]; // ─── Tests ─── describe('useConversations', () => { test('fetches conversation list', async () => { mockList.mockResolvedValue(fakeConversations); const { result } = renderHook(() => useConversations(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); expect(result.current.data).toEqual(fakeConversations); }); }); describe('useCreateConversation', () => { test('adds new conversation to list cache on success', async () => { const newConversation = { id: 'c2', user_id: 'u1', started_at: '2026-01-01T00:00:00Z', status: 'active', }; mockCreate.mockResolvedValue(newConversation); const wrapper = createWrapper(); queryClient.setQueryData(conversationKeys.lists(), fakeConversations); const { result } = renderHook(() => useCreateConversation(), { wrapper }); await result.current.mutateAsync(); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); const list = queryClient.getQueryData(conversationKeys.lists()) as Array<{ id: string; }>; expect(list[0].id).toBe('c2'); expect(list).toHaveLength(2); }); }); describe('useDeleteConversation', () => { test('removes conversation from list cache on success', async () => { mockDelete.mockResolvedValue({ message: 'ok' }); const wrapper = createWrapper(); queryClient.setQueryData(conversationKeys.lists(), fakeConversations); const { result } = renderHook(() => useDeleteConversation(), { wrapper }); await result.current.mutateAsync('c1'); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); const list = queryClient.getQueryData(conversationKeys.lists()) as Array<{ id: string; }>; expect(list).toHaveLength(0); }); });