chore: update test file

This commit is contained in:
Wendong-Fan 2025-11-08 15:46:21 +08:00
parent 34cc128d1a
commit 746064aebe
6 changed files with 475 additions and 352 deletions

View file

@ -4,46 +4,86 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { BrowserRouter } from 'react-router-dom'
import ChatBox from '../../../src/components/ChatBox/index'
import { useChatStore } from '../../../src/store/chatStore'
import { useAuthStore } from '../../../src/store/authStore'
import * as fetchApi from '../../../src/api/http'
const { fetchPost, proxyFetchGet } = fetchApi
// Mock dependencies (use the same relative paths as the imports above)
vi.mock('../../../src/store/chatStore', () => ({ useChatStore: vi.fn() }))
vi.mock('../../../src/store/authStore', () => ({ useAuthStore: vi.fn() }))
vi.mock('../../../src/api/http', () => ({
fetchPost: vi.fn(),
vi.mock('../../../src/api/http', () => ({
fetchPost: vi.fn(),
proxyFetchGet: vi.fn(),
proxyFetchPut: vi.fn()
}))
// Also mock the alias paths the component uses so the component picks up these mocks
vi.mock('@/store/chatStore', () => ({ useChatStore: vi.fn() }))
vi.mock('@/store/authStore', () => ({ useAuthStore: vi.fn() }))
vi.mock('@/api/http', () => ({
fetchPost: vi.fn(),
vi.mock('@/api/http', () => ({
fetchPost: vi.fn(),
proxyFetchGet: vi.fn(),
proxyFetchPut: vi.fn()
}))
vi.mock('../../../src/lib', () => ({
generateUniqueId: vi.fn(() => 'test-unique-id')
generateUniqueId: vi.fn(() => 'test-unique-id'),
replayActiveTask: vi.fn()
}))
// Mock BottomInput component
vi.mock('../../../src/components/ChatBox/BottomInput', () => ({
BottomInput: vi.fn(({ onSend, message, onMessageChange }: any) => (
<div data-testid="bottom-input">
<input
data-testid="message-input"
placeholder="Type your message..."
value={message}
onChange={(e) => onMessageChange(e.target.value)}
/>
<button data-testid="send-button" onClick={() => onSend()}>
Send
</button>
</div>
))
// Mock projectStore with proper vanilla store structure
vi.mock('../../../src/store/projectStore', () => ({
useProjectStore: vi.fn()
}))
// Mock useChatStoreAdapter to provide both stores
vi.mock('../../../src/hooks/useChatStoreAdapter', () => ({
default: vi.fn()
}))
// Mock i18next for translations
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => {
const translations: Record<string, string> = {
'layout.welcome-to-eigent': 'Welcome to Eigent',
'layout.how-can-i-help-you': 'How can I help you today?',
'layout.palm-springs-tennis-trip-planner': 'Palm Springs Tennis Trip Planner',
'layout.bank-transfer-csv-analysis': 'Bank Transfer CSV Analysis and Visualization',
'layout.find-duplicate-files': 'Find Duplicate Files in Downloads Folder',
'layout.palm-springs-tennis-trip-planner-message': 'Plan a 3-day tennis trip to Palm Springs',
'layout.bank-transfer-csv-analysis-message': 'Analyze bank transfer CSV',
'layout.find-duplicate-files-message': 'Find duplicate files',
'chat.ask-placeholder': 'Type your message...',
'layout.by-messaging-eigent': 'By messaging Eigent, you agree to our',
'layout.terms-of-use': 'Terms of Use',
'layout.and': 'and',
'layout.privacy-policy': 'Privacy Policy'
}
return translations[key] || key
}
})
}))
// Mock BottomBox component
vi.mock('../../../src/components/ChatBox/BottomBox', () => ({
default: vi.fn(({ inputProps }: any) => {
if (!inputProps) return null
return (
<div data-testid="bottom-box">
<input
data-testid="message-input"
placeholder={inputProps.placeholder}
value={inputProps.value}
onChange={(e) => inputProps.onChange(e.target.value)}
/>
<button data-testid="send-button" onClick={() => inputProps.onSend()}>
Send
</button>
</div>
)
})
}))
// Mock ProjectChatContainer to avoid scrollTo issues
vi.mock('../../../src/components/ChatBox/ProjectChatContainer', () => ({
ProjectChatContainer: vi.fn(() => <div data-testid="project-chat-container">Chat Container</div>)
}))
// Mock other components
@ -66,7 +106,7 @@ vi.mock('../../../src/components/ChatBox/TypeCardSkeleton', () => ({
}))
vi.mock('../../../src/components/Dialog/Privacy', () => ({
PrivacyDialog: vi.fn(({ open, onOpenChange }: any) =>
PrivacyDialog: vi.fn(({ open, onOpenChange }: any) =>
open ? (
<div data-testid="privacy-dialog">
Privacy Dialog
@ -76,12 +116,15 @@ vi.mock('../../../src/components/Dialog/Privacy', () => ({
)
}))
describe('ChatBox Component', () => {
const mockUseChatStore = vi.mocked(useChatStore)
describe('ChatBox Component', async () => {
const mockUseAuthStore = vi.mocked(useAuthStore)
const mockFetchPost = vi.mocked(fetchPost)
const mockProxyFetchGet = vi.mocked(proxyFetchGet)
// Import the mocked hook
const mockUseChatStoreAdapter = vi.mocked((await import('../../../src/hooks/useChatStoreAdapter')).default)
const mockUseProjectStore = vi.mocked((await import('../../../src/store/projectStore')).useProjectStore)
const defaultChatStoreState = {
activeTaskId: 'test-task-id',
tasks: {
@ -103,7 +146,8 @@ describe('ChatBox Component', () => {
cotList: [],
activeWorkSpace: null,
snapshots: [],
isTaskEdit: false
isTaskEdit: false,
isContextExceeded: false
}
},
setHasMessages: vi.fn(),
@ -122,7 +166,44 @@ describe('ChatBox Component', () => {
setIsTaskEdit: vi.fn(),
addTaskInfo: vi.fn(),
updateTaskInfo: vi.fn(),
deleteTaskInfo: vi.fn()
deleteTaskInfo: vi.fn(),
getFormattedTaskTime: vi.fn(() => '00:00:00'),
setAttaches: vi.fn(),
setNextTaskId: vi.fn(),
removeTask: vi.fn(),
setElapsed: vi.fn(),
setTaskTime: vi.fn(),
setStatus: vi.fn()
}
const defaultProjectStoreState = {
activeProjectId: 'test-project-id',
projects: {},
createProject: vi.fn(),
setActiveProject: vi.fn(),
removeProject: vi.fn(),
updateProject: vi.fn(),
replayProject: vi.fn(),
addQueuedMessage: vi.fn(),
removeQueuedMessage: vi.fn(),
restoreQueuedMessage: vi.fn(),
clearQueuedMessages: vi.fn(),
createChatStore: vi.fn(),
appendInitChatStore: vi.fn(),
setActiveChatStore: vi.fn(),
removeChatStore: vi.fn(),
saveChatStore: vi.fn(),
getChatStore: vi.fn(),
getActiveChatStore: vi.fn(() => ({
getState: () => defaultChatStoreState,
subscribe: () => () => {}
})),
getAllChatStores: vi.fn(() => []),
getAllProjects: vi.fn(),
getProjectById: vi.fn(() => ({ queuedMessages: [] })),
getProjectTotalTokens: vi.fn(),
setHistoryId: vi.fn(),
getHistoryId: vi.fn()
}
const defaultAuthStoreState = {
@ -132,9 +213,13 @@ describe('ChatBox Component', () => {
beforeEach(() => {
// Reset all mocks
vi.clearAllMocks()
// Setup default store states
mockUseChatStore.mockReturnValue(defaultChatStoreState as any)
mockUseChatStoreAdapter.mockReturnValue({
projectStore: defaultProjectStoreState as any,
chatStore: defaultChatStoreState as any
})
mockUseProjectStore.mockReturnValue(defaultProjectStoreState as any)
mockUseAuthStore.mockReturnValue(defaultAuthStoreState as any)
// Setup default API responses
@ -179,15 +264,15 @@ describe('ChatBox Component', () => {
describe('Initial Render', () => {
it('should render welcome screen when no messages exist', () => {
renderChatBox()
expect(screen.getByText('Welcome to Eigent')).toBeInTheDocument()
expect(screen.getByText('How can I help you today?')).toBeInTheDocument()
})
it('should render bottom input component', () => {
it('should render bottom box component', () => {
renderChatBox()
expect(screen.getByTestId('bottom-input')).toBeInTheDocument()
expect(screen.getByTestId('bottom-box')).toBeInTheDocument()
})
it('should fetch privacy settings on mount', async () => {
@ -275,7 +360,7 @@ describe('ChatBox Component', () => {
describe('Chat Interface', () => {
beforeEach(() => {
mockUseChatStore.mockReturnValue({
const updatedChatState = {
...defaultChatStoreState,
tasks: {
'test-task-id': {
@ -297,34 +382,68 @@ describe('ChatBox Component', () => {
hasMessages: true
}
}
} as any)
}
mockUseChatStoreAdapter.mockReturnValue({
projectStore: defaultProjectStoreState as any,
chatStore: updatedChatState as any
})
})
it('should render chat messages when they exist', () => {
it('should render project chat container when messages exist', () => {
renderChatBox()
expect(screen.getByTestId('message-user')).toHaveTextContent('Hello')
expect(screen.getByTestId('message-assistant')).toHaveTextContent('Hi there!')
expect(screen.getByTestId('project-chat-container')).toBeInTheDocument()
})
it('should handle message sending', async () => {
const user = userEvent.setup()
// Create a proper pending state where we can continue a conversation
const updatedChatState = {
...defaultChatStoreState,
tasks: {
'test-task-id': {
...defaultChatStoreState.tasks['test-task-id'],
messages: [
{
id: '1',
role: 'user',
content: 'Hello',
attaches: []
},
{
id: '2',
role: 'assistant',
content: 'Hi there!',
step: 'wait_confirm', // Add wait_confirm to allow continuation
attaches: []
}
],
hasMessages: true,
hasWaitComfirm: true, // Set hasWaitComfirm to true
status: 'pending' // Keep it pending
}
}
}
mockUseChatStoreAdapter.mockReturnValue({
projectStore: defaultProjectStoreState as any,
chatStore: updatedChatState as any
})
renderChatBox()
const messageInput = screen.getByTestId('message-input')
const sendButton = screen.getByTestId('send-button')
await user.type(messageInput, 'Test message')
await user.click(sendButton)
expect(defaultChatStoreState.addMessages).toHaveBeenCalledWith(
'test-task-id',
expect.objectContaining({
role: 'user',
content: 'Test message'
})
)
// The component should call fetchPost for continuing conversation
await waitFor(() => {
expect(mockFetchPost).toHaveBeenCalled()
})
})
it('should not send empty messages', async () => {
@ -340,8 +459,10 @@ describe('ChatBox Component', () => {
})
describe('Task Management', () => {
it('should render task card when step is to_sub_tasks', () => {
mockUseChatStore.mockReturnValue({
it('should render project chat container when tasks have messages', () => {
mockUseChatStoreAdapter.mockReturnValue({
projectStore: defaultProjectStoreState as any,
chatStore: {
...defaultChatStoreState,
tasks: {
'test-task-id': {
@ -360,15 +481,19 @@ describe('ChatBox Component', () => {
cotList: []
}
}
} as any)
} as any
})
renderChatBox()
expect(screen.getByTestId('task-card')).toBeInTheDocument()
// With the new architecture, task cards are rendered inside ProjectChatContainer
expect(screen.getByTestId('project-chat-container')).toBeInTheDocument()
})
it('should render notice card when appropriate', () => {
mockUseChatStore.mockReturnValue({
it('should render project chat container for notice card scenario', () => {
mockUseChatStoreAdapter.mockReturnValue({
projectStore: defaultProjectStoreState as any,
chatStore: {
...defaultChatStoreState,
tasks: {
'test-task-id': {
@ -386,17 +511,21 @@ describe('ChatBox Component', () => {
cotList: ['item1']
}
}
} as any)
} as any
})
renderChatBox()
expect(screen.getByTestId('notice-card')).toBeInTheDocument()
// With the new architecture, notice cards are rendered inside ProjectChatContainer
expect(screen.getByTestId('project-chat-container')).toBeInTheDocument()
})
})
describe('Loading States', () => {
it('should show skeleton when task is pending', () => {
mockUseChatStore.mockReturnValue({
it('should render project chat container when task is pending', () => {
mockUseChatStoreAdapter.mockReturnValue({
projectStore: defaultProjectStoreState as any,
chatStore: {
...defaultChatStoreState,
tasks: {
'test-task-id': {
@ -413,17 +542,21 @@ describe('ChatBox Component', () => {
isTakeControl: false
}
}
} as any)
} as any
})
renderChatBox()
expect(screen.getByTestId('skeleton')).toBeInTheDocument()
// With the new architecture, loading states are handled inside ProjectChatContainer
expect(screen.getByTestId('project-chat-container')).toBeInTheDocument()
})
})
describe('File Handling', () => {
it('should render file list when message has end step with files', () => {
mockUseChatStore.mockReturnValue({
it('should render project chat container when message has files', () => {
mockUseChatStoreAdapter.mockReturnValue({
projectStore: defaultProjectStoreState as any,
chatStore: {
...defaultChatStoreState,
tasks: {
'test-task-id': {
@ -446,18 +579,19 @@ describe('ChatBox Component', () => {
hasMessages: true
}
}
} as any)
} as any
})
renderChatBox()
expect(screen.getByText('test-file')).toBeInTheDocument()
expect(screen.getByText('PDF')).toBeInTheDocument()
// With the new architecture, file lists are rendered inside ProjectChatContainer
expect(screen.getByTestId('project-chat-container')).toBeInTheDocument()
})
it('should handle file selection', async () => {
const user = userEvent.setup()
mockUseChatStore.mockReturnValue({
it('should render project chat container for file handling', () => {
mockUseChatStoreAdapter.mockReturnValue({
projectStore: defaultProjectStoreState as any,
chatStore: {
...defaultChatStoreState,
tasks: {
'test-task-id': {
@ -480,34 +614,23 @@ describe('ChatBox Component', () => {
hasMessages: true
}
}
} as any)
} as any
})
renderChatBox()
const fileElement = screen.getByText('test-file').closest('div')
if (fileElement) {
await user.click(fileElement)
expect(defaultChatStoreState.setSelectedFile).toHaveBeenCalledWith(
'test-task-id',
expect.objectContaining({
name: 'test-file.pdf',
type: 'PDF'
})
)
expect(defaultChatStoreState.setActiveWorkSpace).toHaveBeenCalledWith(
'test-task-id',
'documentWorkSpace'
)
}
// With the new architecture, file lists are rendered inside ProjectChatContainer
expect(screen.getByTestId('project-chat-container')).toBeInTheDocument()
})
})
describe('Agent Interaction', () => {
it('should handle human reply when activeAsk is set', async () => {
const user = userEvent.setup()
mockUseChatStore.mockReturnValue({
mockUseChatStoreAdapter.mockReturnValue({
projectStore: defaultProjectStoreState as any,
chatStore: {
...defaultChatStoreState,
tasks: {
'test-task-id': {
@ -517,19 +640,21 @@ describe('ChatBox Component', () => {
hasMessages: true
}
}
} as any)
} as any
})
renderChatBox()
const messageInput = screen.getByTestId('message-input')
const sendButton = screen.getByTestId('send-button')
await user.type(messageInput, 'Test reply')
await user.click(sendButton)
await waitFor(() => {
// The API call now uses project ID instead of task ID
expect(mockFetchPost).toHaveBeenCalledWith(
'/chat/test-task-id/human-reply',
'/chat/test-project-id/human-reply',
{
agent: 'test-agent',
reply: 'Test reply'
@ -561,9 +686,12 @@ describe('ChatBox Component', () => {
}
} as any
mockUseChatStore.mockReturnValue(storeObj)
mockUseChatStoreAdapter.mockReturnValue({
projectStore: defaultProjectStoreState as any,
chatStore: storeObj
})
renderChatBox()
renderChatBox()
// Type a non-empty message so handleSend proceeds to process the ask list
const messageInput = screen.getByTestId('message-input')
@ -688,20 +816,34 @@ describe('ChatBox Component', () => {
})
describe('Keyboard Shortcuts', () => {
it('should handle Ctrl+Enter keyboard shortcut', async () => {
it('should handle message sending through send button', async () => {
const user = userEvent.setup()
// Set up a state where we can send messages
const mockStartTask = vi.fn().mockResolvedValue(undefined)
const stateForSending = {
...defaultChatStoreState,
startTask: mockStartTask
}
mockUseChatStoreAdapter.mockReturnValue({
projectStore: defaultProjectStoreState as any,
chatStore: stateForSending as any
})
renderChatBox()
const messageInput = screen.getByTestId('message-input')
await user.type(messageInput, 'Test message')
// Simulate Ctrl+Enter
// Not all test environments simulate Ctrl+Enter handlers; click the send button instead
const sendButton = screen.getByTestId('send-button')
await user.click(sendButton)
expect(defaultChatStoreState.addMessages).toHaveBeenCalled()
// Click the send button instead of testing Ctrl+Enter
const sendButton = screen.getByTestId('send-button')
await user.click(sendButton)
// Should call startTask for a new conversation
await waitFor(() => {
expect(mockStartTask).toHaveBeenCalled()
})
})
})
@ -712,7 +854,9 @@ describe('ChatBox Component', () => {
mockFetchPost.mockRejectedValue(new Error('API Error'))
// Force a code path that calls fetchPost by setting activeAsk on the task
mockUseChatStore.mockReturnValue({
mockUseChatStoreAdapter.mockReturnValue({
projectStore: defaultProjectStoreState as any,
chatStore: {
...defaultChatStoreState,
tasks: {
'test-task-id': {
@ -721,7 +865,8 @@ describe('ChatBox Component', () => {
hasMessages: true
}
}
} as any)
} as any
})
renderChatBox()