Files
life-echo/app-expo/tests/features/conversation/hooks.test.tsx
Kevin 1374f6e8f5 feat(memoir+conversation): 章节/故事最小可读字数;会话 hasUserMessage 与 UI 优化
- 后端:300 字门槛统一物化、hydrate、列表/PDF/详情;过短章节对读者隐藏
- 对话:首包前打字动画、大字模式排版、朗读/TTS 交互与布局稳定
- 首页:复用无用户消息会话;空列表「继续对话」与文案 i18n
- 章节阅读:标题进正文、封面与去重标题;阅读 Markdown 字号上调
2026-03-26 16:28:33 +08:00

136 lines
3.4 KiB
TypeScript

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 (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
}
afterEach(async () => {
await queryClient?.cancelQueries();
queryClient?.clear();
jest.clearAllMocks();
});
const fakeConversations = [
{
id: 'c1',
title: '第一次对话',
avatarUrl: null,
latestMessagePreview: '你好',
latestMessageTime: 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);
});
});