import type { RefObject } from 'react' import type { ChatConfig } from '../types' import type { AppData, AppMeta, ConversationItem } from '@/models/share' import { render, screen } from '@testing-library/react' import { vi } from 'vitest' import { useGlobalPublicStore } from '@/context/global-public-context' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { defaultSystemFeatures } from '@/types/feature' import { useEmbeddedChatbot } from './hooks' import EmbeddedChatbot from './index' vi.mock('./hooks', () => ({ useEmbeddedChatbot: vi.fn(), })) vi.mock('@/hooks/use-breakpoints', () => ({ default: vi.fn(), MediaType: { mobile: 'mobile', tablet: 'tablet', pc: 'pc', }, })) vi.mock('@/hooks/use-document-title', () => ({ default: vi.fn(), })) vi.mock('@/context/global-public-context', () => ({ useGlobalPublicStore: vi.fn(), })) vi.mock('./chat-wrapper', () => ({ __esModule: true, default: () =>
chat area
, })) vi.mock('./header', () => ({ __esModule: true, default: () =>
chat header
, })) vi.mock('./theme/theme-context', () => ({ useThemeContext: vi.fn(() => ({ buildTheme: vi.fn(), theme: { backgroundHeaderColorStyle: '', }, })), })) const mockIsDify = vi.fn(() => false) vi.mock('./utils', () => ({ isDify: () => mockIsDify(), })) type EmbeddedChatbotHookReturn = ReturnType const createHookReturn = (overrides: Partial = {}): EmbeddedChatbotHookReturn => { const appData: AppData = { app_id: 'app-1', can_replace_logo: true, custom_config: { remove_webapp_brand: false, replace_webapp_logo: '', }, enable_site: true, end_user_id: 'user-1', site: { title: 'Embedded App', chat_color_theme: 'blue', chat_color_theme_inverted: false, }, } const base: EmbeddedChatbotHookReturn = { appSourceType: 'webApp' as EmbeddedChatbotHookReturn['appSourceType'], isInstalledApp: false, appId: 'app-1', currentConversationId: '', currentConversationItem: undefined, removeConversationIdInfo: vi.fn(), handleConversationIdInfoChange: vi.fn(), appData, appParams: {} as ChatConfig, appMeta: { tool_icons: {} } as AppMeta, appPinnedConversationData: { data: [], has_more: false, limit: 20 }, appConversationData: { data: [], has_more: false, limit: 20 }, appConversationDataLoading: false, appChatListData: { data: [], has_more: false, limit: 20 }, appChatListDataLoading: false, appPrevChatList: [], pinnedConversationList: [] as ConversationItem[], conversationList: [] as ConversationItem[], setShowNewConversationItemInList: vi.fn(), newConversationInputs: {}, newConversationInputsRef: { current: {} } as unknown as RefObject>, handleNewConversationInputsChange: vi.fn(), inputsForms: [], handleNewConversation: vi.fn(), handleStartChat: vi.fn(), handleChangeConversation: vi.fn(), handleNewConversationCompleted: vi.fn(), newConversationId: '', chatShouldReloadKey: 'reload-key', allowResetChat: true, handleFeedback: vi.fn(), currentChatInstanceRef: { current: { handleStop: vi.fn() } }, clearChatList: false, setClearChatList: vi.fn(), isResponding: false, setIsResponding: vi.fn(), currentConversationInputs: {}, setCurrentConversationInputs: vi.fn(), allInputsHidden: false, initUserVariables: {}, } return { ...base, ...overrides, } } describe('EmbeddedChatbot index', () => { beforeEach(() => { vi.clearAllMocks() vi.mocked(useBreakpoints).mockReturnValue(MediaType.mobile) vi.mocked(useEmbeddedChatbot).mockReturnValue(createHookReturn()) vi.mocked(useGlobalPublicStore).mockImplementation(selector => selector({ systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: true, workspace_logo: '', }, }, setSystemFeatures: vi.fn(), })) }) describe('Loading and chat content', () => { it('should show loading state before chat content', () => { vi.mocked(useEmbeddedChatbot).mockReturnValue(createHookReturn({ appChatListDataLoading: true })) render() expect(screen.getByRole('status')).toBeInTheDocument() expect(screen.queryByText('chat area')).not.toBeInTheDocument() }) it('should render chat content when loading finishes', () => { render() expect(screen.getByText('chat area')).toBeInTheDocument() }) }) describe('Powered by branding', () => { it('should show workspace logo on mobile when branding is enabled', () => { vi.mocked(useGlobalPublicStore).mockImplementation(selector => selector({ systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: true, workspace_logo: 'https://example.com/workspace-logo.png', }, }, setSystemFeatures: vi.fn(), })) render() expect(screen.getByText('share.chat.poweredBy')).toBeInTheDocument() expect(screen.getByAltText('logo')).toHaveAttribute('src', 'https://example.com/workspace-logo.png') }) it('should show custom logo when workspace branding logo is unavailable', () => { vi.mocked(useEmbeddedChatbot).mockReturnValue(createHookReturn({ appData: { app_id: 'app-1', can_replace_logo: true, custom_config: { remove_webapp_brand: false, replace_webapp_logo: 'https://example.com/custom-logo.png', }, enable_site: true, end_user_id: 'user-1', site: { title: 'Embedded App', chat_color_theme: 'blue', chat_color_theme_inverted: false, }, }, })) render() expect(screen.getByText('share.chat.poweredBy')).toBeInTheDocument() expect(screen.getByAltText('logo')).toHaveAttribute('src', 'https://example.com/custom-logo.png') }) it('should hide powered by section when branding is removed', () => { vi.mocked(useEmbeddedChatbot).mockReturnValue(createHookReturn({ appData: { app_id: 'app-1', can_replace_logo: true, custom_config: { remove_webapp_brand: true, replace_webapp_logo: '', }, enable_site: true, end_user_id: 'user-1', site: { title: 'Embedded App', chat_color_theme: 'blue', chat_color_theme_inverted: false, }, }, })) render() expect(screen.queryByText('share.chat.poweredBy')).not.toBeInTheDocument() }) it('should not show powered by section on desktop', () => { vi.mocked(useBreakpoints).mockReturnValue(MediaType.pc) vi.mocked(useEmbeddedChatbot).mockReturnValue(createHookReturn({ appData: null })) mockIsDify.mockReturnValue(true) render() expect(screen.queryByText('share.chat.poweredBy')).not.toBeInTheDocument() expect(screen.getByText('chat header')).toBeInTheDocument() }) }) })