improve tests

This commit is contained in:
yyh 2025-12-27 12:58:54 +08:00
parent 2365d237b3
commit c4336dc560
No known key found for this signature in database
3 changed files with 146 additions and 24 deletions

View File

@ -3,6 +3,7 @@ import type { ChatConfig } from '../types'
import type { AppConversationData, AppData, AppMeta, ConversationItem } from '@/models/share'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { act, renderHook, waitFor } from '@testing-library/react'
import { ToastProvider } from '@/app/components/base/toast'
import {
fetchChatList,
fetchConversations,
@ -12,14 +13,6 @@ import { shareQueryKeys } from '@/service/use-share'
import { CONVERSATION_ID_INFO } from '../constants'
import { useChatWithHistory } from './hooks'
const notifyMock = vi.fn()
vi.mock('@/app/components/base/toast', () => ({
useToastContext: () => ({
notify: notifyMock,
}),
}))
vi.mock('@/hooks/use-app-favicon', () => ({
useAppFavicon: vi.fn(),
}))
@ -85,7 +78,9 @@ const createQueryClient = () => new QueryClient({
const createWrapper = (queryClient: QueryClient) => {
return ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
<QueryClientProvider client={queryClient}>
<ToastProvider>{children}</ToastProvider>
</QueryClientProvider>
)
}
@ -127,6 +122,7 @@ const setConversationIdInfo = (appId: string, conversationId: string) => {
describe('useChatWithHistory', () => {
beforeEach(() => {
vi.clearAllMocks()
localStorage.removeItem(CONVERSATION_ID_INFO)
mockStoreState.appInfo = {
app_id: 'app-1',
custom_config: null,
@ -142,6 +138,10 @@ describe('useChatWithHistory', () => {
setConversationIdInfo('app-1', 'conversation-1')
})
afterEach(() => {
localStorage.removeItem(CONVERSATION_ID_INFO)
})
// Scenario: share query results populate conversation lists and trigger chat list fetch.
describe('Share queries', () => {
it('should load pinned, unpinned, and chat list data from share queries', async () => {
@ -208,4 +208,63 @@ describe('useChatWithHistory', () => {
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: shareQueryKeys.conversations })
})
})
// Scenario: chat list queries stop when reload key is cleared.
describe('Chat list gating', () => {
it('should not refetch chat list when newConversationId matches current conversation', async () => {
// Arrange
const listData = createConversationData({
data: [createConversationItem({ id: 'conversation-1', name: 'First' })],
})
mockFetchConversations.mockResolvedValue(listData)
mockFetchChatList.mockResolvedValue({ data: [] })
mockGenerationConversationName.mockResolvedValue(createConversationItem({ id: 'conversation-1' }))
const { result } = renderWithClient(() => useChatWithHistory())
await waitFor(() => {
expect(mockFetchChatList).toHaveBeenCalledTimes(1)
})
// Act
act(() => {
result.current.handleNewConversationCompleted('conversation-1')
})
// Assert
await waitFor(() => {
expect(result.current.chatShouldReloadKey).toBe('')
})
expect(mockFetchChatList).toHaveBeenCalledTimes(1)
})
})
// Scenario: conversation id updates persist to localStorage.
describe('Conversation id persistence', () => {
it('should store new conversation id in localStorage after completion', async () => {
// Arrange
const listData = createConversationData({
data: [createConversationItem({ id: 'conversation-1', name: 'First' })],
})
mockFetchConversations.mockResolvedValue(listData)
mockFetchChatList.mockResolvedValue({ data: [] })
mockGenerationConversationName.mockResolvedValue(createConversationItem({ id: 'conversation-new' }))
const { result } = renderWithClient(() => useChatWithHistory())
// Act
act(() => {
result.current.handleNewConversationCompleted('conversation-new')
})
// Assert
await waitFor(() => {
const storedValue = localStorage.getItem(CONVERSATION_ID_INFO)
const parsed = storedValue ? JSON.parse(storedValue) : {}
const storedUserId = parsed['app-1']?.['user-1']
const storedDefaultId = parsed['app-1']?.DEFAULT
expect([storedUserId, storedDefaultId]).toContain('conversation-new')
})
})
})
})

View File

@ -3,22 +3,16 @@ import type { ChatConfig } from '../types'
import type { AppConversationData, AppData, AppMeta, ConversationItem } from '@/models/share'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { act, renderHook, waitFor } from '@testing-library/react'
import { ToastProvider } from '@/app/components/base/toast'
import {
fetchChatList,
fetchConversations,
generationConversationName,
} from '@/service/share'
import { shareQueryKeys } from '@/service/use-share'
import { CONVERSATION_ID_INFO } from '../constants'
import { useEmbeddedChatbot } from './hooks'
const notifyMock = vi.fn()
vi.mock('@/app/components/base/toast', () => ({
useToastContext: () => ({
notify: notifyMock,
}),
}))
vi.mock('@/i18n-config/i18next-config', () => ({
changeLanguage: vi.fn().mockResolvedValue(undefined),
}))
@ -80,7 +74,9 @@ const createQueryClient = () => new QueryClient({
const createWrapper = (queryClient: QueryClient) => {
return ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
<QueryClientProvider client={queryClient}>
<ToastProvider>{children}</ToastProvider>
</QueryClientProvider>
)
}
@ -112,6 +108,7 @@ const createConversationData = (overrides: Partial<AppConversationData> = {}): A
describe('useEmbeddedChatbot', () => {
beforeEach(() => {
vi.clearAllMocks()
localStorage.removeItem(CONVERSATION_ID_INFO)
mockStoreState.appInfo = {
app_id: 'app-1',
custom_config: null,
@ -128,6 +125,10 @@ describe('useEmbeddedChatbot', () => {
mockStoreState.embeddedUserId = 'embedded-user-1'
})
afterEach(() => {
localStorage.removeItem(CONVERSATION_ID_INFO)
})
// Scenario: share query results populate conversation lists and trigger chat list fetch.
describe('Share queries', () => {
it('should load pinned, unpinned, and chat list data from share queries', async () => {
@ -194,4 +195,63 @@ describe('useEmbeddedChatbot', () => {
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: shareQueryKeys.conversations })
})
})
// Scenario: chat list queries stop when reload key is cleared.
describe('Chat list gating', () => {
it('should not refetch chat list when newConversationId matches current conversation', async () => {
// Arrange
const listData = createConversationData({
data: [createConversationItem({ id: 'conversation-1', name: 'First' })],
})
mockFetchConversations.mockResolvedValue(listData)
mockFetchChatList.mockResolvedValue({ data: [] })
mockGenerationConversationName.mockResolvedValue(createConversationItem({ id: 'conversation-1' }))
const { result } = renderWithClient(() => useEmbeddedChatbot())
await waitFor(() => {
expect(mockFetchChatList).toHaveBeenCalledTimes(1)
})
// Act
act(() => {
result.current.handleNewConversationCompleted('conversation-1')
})
// Assert
await waitFor(() => {
expect(result.current.chatShouldReloadKey).toBe('')
})
expect(mockFetchChatList).toHaveBeenCalledTimes(1)
})
})
// Scenario: conversation id updates persist to localStorage.
describe('Conversation id persistence', () => {
it('should store new conversation id in localStorage after completion', async () => {
// Arrange
const listData = createConversationData({
data: [createConversationItem({ id: 'conversation-1', name: 'First' })],
})
mockFetchConversations.mockResolvedValue(listData)
mockFetchChatList.mockResolvedValue({ data: [] })
mockGenerationConversationName.mockResolvedValue(createConversationItem({ id: 'conversation-new' }))
const { result } = renderWithClient(() => useEmbeddedChatbot())
// Act
act(() => {
result.current.handleNewConversationCompleted('conversation-new')
})
// Assert
await waitFor(() => {
const storedValue = localStorage.getItem(CONVERSATION_ID_INFO)
const parsed = storedValue ? JSON.parse(storedValue) : {}
const storedUserId = parsed['app-1']?.['embedded-user-1']
const storedDefaultId = parsed['app-1']?.DEFAULT
expect([storedUserId, storedDefaultId]).toContain('conversation-new')
})
})
})
})

View File

@ -105,12 +105,13 @@ describe('useShareConversations', () => {
}
// Act
renderShareHook(() => useShareConversations(params))
const { result } = renderShareHook(() => useShareConversations(params))
// Assert
await waitFor(() => {
expect(mockFetchConversations).not.toHaveBeenCalled()
expect(result.current.fetchStatus).toBe('idle')
})
expect(mockFetchConversations).not.toHaveBeenCalled()
})
})
@ -151,12 +152,13 @@ describe('useShareChatList', () => {
}
// Act
renderShareHook(() => useShareChatList(params))
const { result } = renderShareHook(() => useShareChatList(params))
// Assert
await waitFor(() => {
expect(mockFetchChatList).not.toHaveBeenCalled()
expect(result.current.fetchStatus).toBe('idle')
})
expect(mockFetchChatList).not.toHaveBeenCalled()
})
})
@ -197,12 +199,13 @@ describe('useShareConversationName', () => {
}
// Act
renderShareHook(() => useShareConversationName(params, { enabled: false }))
const { result } = renderShareHook(() => useShareConversationName(params, { enabled: false }))
// Assert
await waitFor(() => {
expect(mockGenerationConversationName).not.toHaveBeenCalled()
expect(result.current.fetchStatus).toBe('idle')
})
expect(mockGenerationConversationName).not.toHaveBeenCalled()
})
})