import type { ReactNode } from 'react' import type { AppConversationData, ConversationItem } from '@/models/share' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { act, renderHook, waitFor } from '@testing-library/react' import { fetchChatList, fetchConversations, generationConversationName, } from './share' import { shareQueryKeys, useInvalidateShareConversations, useShareChatList, useShareConversationName, useShareConversations, } from './use-share' vi.mock('./share', () => ({ fetchChatList: vi.fn(), fetchConversations: vi.fn(), generationConversationName: vi.fn(), fetchAppInfo: vi.fn(), fetchAppMeta: vi.fn(), fetchAppParams: vi.fn(), getAppAccessModeByAppCode: vi.fn(), })) const mockFetchConversations = vi.mocked(fetchConversations) const mockFetchChatList = vi.mocked(fetchChatList) const mockGenerationConversationName = vi.mocked(generationConversationName) const createQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false, }, }, }) const createWrapper = (queryClient: QueryClient) => { return ({ children }: { children: ReactNode }) => ( {children} ) } const renderShareHook = (hook: () => T) => { const queryClient = createQueryClient() const wrapper = createWrapper(queryClient) return { queryClient, ...renderHook(hook, { wrapper }), } } const createConversationItem = (overrides: Partial = {}): ConversationItem => ({ id: 'conversation-1', name: 'Conversation 1', inputs: null, introduction: 'Intro', ...overrides, }) const createConversationData = (overrides: Partial = {}): AppConversationData => ({ data: [createConversationItem()], has_more: false, limit: 20, ...overrides, }) // Scenario: share conversation list queries behave consistently with params and enablement. describe('useShareConversations', () => { beforeEach(() => { vi.clearAllMocks() }) it('should fetch conversations when enabled for non-installed apps', async () => { // Arrange const params = { isInstalledApp: false, appId: undefined, pinned: true, limit: 50, } const response = createConversationData() mockFetchConversations.mockResolvedValueOnce(response) // Act const { result, queryClient } = renderShareHook(() => useShareConversations(params)) // Assert await waitFor(() => { expect(mockFetchConversations).toHaveBeenCalledWith(false, undefined, undefined, true, 50) }) await waitFor(() => { expect(result.current.data).toEqual(response) }) expect(queryClient.getQueryCache().find({ queryKey: shareQueryKeys.conversationList(params) })).toBeDefined() }) it('should not fetch conversations when installed app lacks appId', async () => { // Arrange const params = { isInstalledApp: true, appId: undefined, } // Act const { result } = renderShareHook(() => useShareConversations(params)) // Assert await waitFor(() => { expect(result.current.fetchStatus).toBe('idle') }) expect(mockFetchConversations).not.toHaveBeenCalled() }) }) // Scenario: chat list queries respect conversation ID and app installation rules. describe('useShareChatList', () => { beforeEach(() => { vi.clearAllMocks() }) it('should fetch chat list when conversationId is provided', async () => { // Arrange const params = { conversationId: 'conversation-1', isInstalledApp: true, appId: 'app-1', } const response = { data: [] } mockFetchChatList.mockResolvedValueOnce(response) // Act const { result } = renderShareHook(() => useShareChatList(params)) // Assert await waitFor(() => { expect(mockFetchChatList).toHaveBeenCalledWith('conversation-1', true, 'app-1') }) await waitFor(() => { expect(result.current.data).toEqual(response) }) }) it('should not fetch chat list when conversationId is empty', async () => { // Arrange const params = { conversationId: '', isInstalledApp: false, appId: undefined, } // Act const { result } = renderShareHook(() => useShareChatList(params)) // Assert await waitFor(() => { expect(result.current.fetchStatus).toBe('idle') }) expect(mockFetchChatList).not.toHaveBeenCalled() }) }) // Scenario: conversation name queries follow enabled flags and installation constraints. describe('useShareConversationName', () => { beforeEach(() => { vi.clearAllMocks() }) it('should fetch conversation name when enabled and conversationId exists', async () => { // Arrange const params = { conversationId: 'conversation-2', isInstalledApp: false, appId: undefined, } const response = createConversationItem({ id: 'conversation-2', name: 'Generated' }) mockGenerationConversationName.mockResolvedValueOnce(response) // Act const { result } = renderShareHook(() => useShareConversationName(params)) // Assert await waitFor(() => { expect(mockGenerationConversationName).toHaveBeenCalledWith(false, undefined, 'conversation-2') }) await waitFor(() => { expect(result.current.data).toEqual(response) }) }) it('should not fetch conversation name when disabled via options', async () => { // Arrange const params = { conversationId: 'conversation-3', isInstalledApp: false, appId: undefined, } // Act const { result } = renderShareHook(() => useShareConversationName(params, { enabled: false })) // Assert await waitFor(() => { expect(result.current.fetchStatus).toBe('idle') }) expect(mockGenerationConversationName).not.toHaveBeenCalled() }) }) // Scenario: invalidation helper clears share conversation caches. describe('useInvalidateShareConversations', () => { beforeEach(() => { vi.clearAllMocks() }) it('should invalidate share conversations query key when invoked', () => { // Arrange const { result, queryClient } = renderShareHook(() => useInvalidateShareConversations()) const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries') // Act act(() => { result.current() }) // Assert expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: shareQueryKeys.conversations }) }) })