mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-30 20:25:33 +00:00
486 lines
15 KiB
TypeScript
486 lines
15 KiB
TypeScript
/**
|
|
* ChatStore Unit Tests - Core Functionality
|
|
*
|
|
* Tests basic chatStore operations:
|
|
* - Task creation and removal
|
|
* - Status management
|
|
* - Token tracking
|
|
* - Message handling
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, vi, MockedFunction } from 'vitest'
|
|
import { act, renderHook } from '@testing-library/react'
|
|
|
|
// Mock dependencies - moved to top before other imports
|
|
vi.mock('@/api/http', () => ({
|
|
fetchPost: vi.fn(),
|
|
fetchPut: vi.fn(),
|
|
getBaseURL: vi.fn(() => Promise.resolve('http://localhost:8000')),
|
|
proxyFetchPost: vi.fn(),
|
|
proxyFetchPut: vi.fn(),
|
|
proxyFetchGet: vi.fn(),
|
|
uploadFile: vi.fn(),
|
|
fetchDelete: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@microsoft/fetch-event-source', () => ({
|
|
fetchEventSource: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('../../../src/store/authStore', () => ({
|
|
useAuthStore: {
|
|
token: null,
|
|
username: null,
|
|
email: null,
|
|
user_id: null,
|
|
appearance: 'light',
|
|
language: 'system',
|
|
isFirstLaunch: true,
|
|
modelType: 'cloud' as const,
|
|
cloud_model_type: 'gpt-4.1' as const,
|
|
initState: 'permissions' as const,
|
|
share_token: null,
|
|
workerListData: {},
|
|
},
|
|
getAuthStore: vi.fn(() => ({
|
|
token: null,
|
|
username: null,
|
|
email: null,
|
|
user_id: null,
|
|
appearance: 'light',
|
|
language: 'system',
|
|
isFirstLaunch: true,
|
|
modelType: 'cloud' as const,
|
|
cloud_model_type: 'gpt-4.1' as const,
|
|
initState: 'permissions' as const,
|
|
share_token: null,
|
|
workerListData: {},
|
|
})),
|
|
useWorkerList: vi.fn(() => [])
|
|
}))
|
|
|
|
import { useChatStore } from '../../../src/store/chatStore'
|
|
import { useProjectStore } from '../../../src/store/projectStore'
|
|
import { generateUniqueId } from '../../../src/lib'
|
|
|
|
// Mock electron IPC
|
|
(global as any).ipcRenderer = {
|
|
invoke: vi.fn((channel, ...args) => {
|
|
if (channel === 'get-system-language') return Promise.resolve('en')
|
|
if (channel === 'get-browser-port') return Promise.resolve(9222)
|
|
if (channel === 'get-env-path') return Promise.resolve('/path/to/env')
|
|
if (channel === 'mcp-list') return Promise.resolve({})
|
|
return Promise.resolve()
|
|
}),
|
|
}
|
|
|
|
describe('ChatStore - Core Functionality', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('Task Creation', () => {
|
|
it('should create a task with unique ID', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId1 = result.current.getState().create()
|
|
const taskId2 = result.current.getState().create()
|
|
|
|
expect(taskId1).toBeDefined()
|
|
expect(taskId2).toBeDefined()
|
|
expect(taskId1).not.toBe(taskId2)
|
|
expect(result.current.getState().tasks[taskId1]).toBeDefined()
|
|
expect(result.current.getState().tasks[taskId2]).toBeDefined()
|
|
})
|
|
})
|
|
|
|
it('should create a task with custom ID', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
const customId = 'custom-task-123'
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create(customId)
|
|
|
|
expect(taskId).toBe(customId)
|
|
expect(result.current.getState().tasks[customId]).toBeDefined()
|
|
})
|
|
})
|
|
|
|
it('should initialize task with correct default state', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
const task = result.current.getState().tasks[taskId]
|
|
|
|
expect(task.status).toBe('pending')
|
|
expect(task.messages).toEqual([])
|
|
expect(task.tokens).toBe(0)
|
|
expect(task.isPending).toBe(false)
|
|
expect(task.hasWaitComfirm).toBe(false)
|
|
expect(task.progressValue).toBe(0)
|
|
expect(task.taskInfo).toEqual([])
|
|
expect(task.taskRunning).toEqual([])
|
|
expect(task.taskAssigning).toEqual([])
|
|
})
|
|
})
|
|
|
|
it('should set task as active when created', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
|
|
expect(result.current.getState().activeTaskId).toBe(taskId)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Task Removal', () => {
|
|
it('should remove a task by ID', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
expect(result.current.getState().tasks[taskId]).toBeDefined()
|
|
|
|
result.current.getState().removeTask(taskId)
|
|
|
|
expect(result.current.getState().tasks[taskId]).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
it('should handle removing non-existent task gracefully', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
// Should not throw
|
|
result.current.getState().removeTask('non-existent-id')
|
|
})
|
|
})
|
|
|
|
it('should clear all tasks and create new one', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId1 = result.current.getState().create()
|
|
const taskId2 = result.current.getState().create()
|
|
|
|
expect(Object.keys(result.current.getState().tasks)).toHaveLength(2)
|
|
|
|
result.current.getState().clearTasks()
|
|
|
|
const remainingTasks = Object.keys(result.current.getState().tasks)
|
|
expect(remainingTasks).toHaveLength(1)
|
|
expect(result.current.getState().activeTaskId).toBeDefined()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Status Management', () => {
|
|
it('should update task status correctly', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
|
|
result.current.getState().setStatus(taskId, 'running')
|
|
expect(result.current.getState().tasks[taskId].status).toBe('running')
|
|
|
|
result.current.getState().setStatus(taskId, 'finished')
|
|
expect(result.current.getState().tasks[taskId].status).toBe('finished')
|
|
|
|
result.current.getState().setStatus(taskId, 'pause')
|
|
expect(result.current.getState().tasks[taskId].status).toBe('pause')
|
|
})
|
|
})
|
|
|
|
it('should set pending state independently of status', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
|
|
result.current.getState().setIsPending(taskId, true)
|
|
expect(result.current.getState().tasks[taskId].isPending).toBe(true)
|
|
expect(result.current.getState().tasks[taskId].status).toBe('pending')
|
|
|
|
result.current.getState().setStatus(taskId, 'running')
|
|
expect(result.current.getState().tasks[taskId].isPending).toBe(true)
|
|
expect(result.current.getState().tasks[taskId].status).toBe('running')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Token Management', () => {
|
|
it('should accumulate tokens correctly', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
|
|
result.current.getState().addTokens(taskId, 100)
|
|
expect(result.current.getState().getTokens(taskId)).toBe(100)
|
|
|
|
result.current.getState().addTokens(taskId, 50)
|
|
expect(result.current.getState().getTokens(taskId)).toBe(150)
|
|
|
|
result.current.getState().addTokens(taskId, 250)
|
|
expect(result.current.getState().getTokens(taskId)).toBe(400)
|
|
})
|
|
})
|
|
|
|
it('should handle negative token additions', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
|
|
result.current.getState().addTokens(taskId, 100)
|
|
result.current.getState().addTokens(taskId, -50)
|
|
|
|
expect(result.current.getState().getTokens(taskId)).toBe(50)
|
|
})
|
|
})
|
|
|
|
it('should return 0 tokens for non-existent task', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
expect(result.current.getState().getTokens('non-existent')).toBe(0)
|
|
})
|
|
|
|
it('should preserve tokens when creating new task with initial tokens', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId1 = result.current.getState().create()
|
|
result.current.getState().addTokens(taskId1, 500)
|
|
|
|
// Simulate new task in same project with accumulated tokens
|
|
const taskId2 = result.current.getState().create()
|
|
result.current.getState().addTokens(taskId2, 500) // Cumulative
|
|
|
|
expect(result.current.getState().getTokens(taskId2)).toBe(500)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Message Management', () => {
|
|
it('should add messages to task', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
|
|
result.current.getState().addMessages(taskId, {
|
|
id: generateUniqueId(),
|
|
role: 'user',
|
|
content: 'Hello, world!'
|
|
})
|
|
|
|
expect(result.current.getState().tasks[taskId].messages).toHaveLength(1)
|
|
expect(result.current.getState().tasks[taskId].messages[0].content).toBe('Hello, world!')
|
|
})
|
|
})
|
|
|
|
it('should maintain message order', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
|
|
result.current.getState().addMessages(taskId, {
|
|
id: '1',
|
|
role: 'user',
|
|
content: 'First'
|
|
})
|
|
result.current.getState().addMessages(taskId, {
|
|
id: '2',
|
|
role: 'agent',
|
|
content: 'Second'
|
|
})
|
|
result.current.getState().addMessages(taskId, {
|
|
id: '3',
|
|
role: 'user',
|
|
content: 'Third'
|
|
})
|
|
|
|
const messages = result.current.getState().tasks[taskId].messages
|
|
expect(messages).toHaveLength(3)
|
|
expect(messages[0].content).toBe('First')
|
|
expect(messages[1].content).toBe('Second')
|
|
expect(messages[2].content).toBe('Third')
|
|
})
|
|
})
|
|
|
|
it('should get last user message', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
result.current.getState().setActiveTaskId(taskId)
|
|
|
|
result.current.getState().addMessages(taskId, {
|
|
id: '1',
|
|
role: 'user',
|
|
content: 'First user message'
|
|
})
|
|
result.current.getState().addMessages(taskId, {
|
|
id: '2',
|
|
role: 'agent',
|
|
content: 'Agent response'
|
|
})
|
|
result.current.getState().addMessages(taskId, {
|
|
id: '3',
|
|
role: 'user',
|
|
content: 'Second user message'
|
|
})
|
|
|
|
const lastUserMessage = result.current.getState().getLastUserMessage()
|
|
expect(lastUserMessage?.content).toBe('Second user message')
|
|
})
|
|
})
|
|
|
|
it('should return null when no user messages exist', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
result.current.getState().setActiveTaskId(taskId)
|
|
|
|
result.current.getState().addMessages(taskId, {
|
|
id: '1',
|
|
role: 'agent',
|
|
content: 'Agent message'
|
|
})
|
|
|
|
const lastUserMessage = result.current.getState().getLastUserMessage()
|
|
expect(lastUserMessage).toBeNull()
|
|
})
|
|
})
|
|
|
|
it('should set messages replacing existing ones', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
|
|
result.current.getState().addMessages(taskId, {
|
|
id: '1',
|
|
role: 'user',
|
|
content: 'Original'
|
|
})
|
|
|
|
const newMessages = [
|
|
{ id: '2', role: 'user' as const, content: 'New 1' },
|
|
{ id: '3', role: 'agent' as const, content: 'New 2' }
|
|
]
|
|
|
|
result.current.getState().setMessages(taskId, newMessages)
|
|
|
|
expect(result.current.getState().tasks[taskId].messages).toHaveLength(2)
|
|
expect(result.current.getState().tasks[taskId].messages[0].content).toBe('New 1')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Task Time Tracking', () => {
|
|
it('should track task time', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
const startTime = Date.now()
|
|
|
|
result.current.getState().setTaskTime(taskId, startTime)
|
|
|
|
expect(result.current.getState().tasks[taskId].taskTime).toBe(startTime)
|
|
})
|
|
})
|
|
|
|
it('should track elapsed time', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
|
|
result.current.getState().setElapsed(taskId, 5000)
|
|
|
|
expect(result.current.getState().tasks[taskId].elapsed).toBe(5000)
|
|
})
|
|
})
|
|
|
|
it('should format task time correctly', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
|
|
// Test elapsed time formatting
|
|
result.current.getState().setTaskTime(taskId, 0)
|
|
result.current.getState().setElapsed(taskId, 3665000) // 1h 1m 5s
|
|
|
|
const formatted = result.current.getState().getFormattedTaskTime(taskId)
|
|
expect(formatted).toBe('01:01:05')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Progress Tracking', () => {
|
|
it('should update progress value', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
|
|
result.current.getState().setProgressValue(taskId, 50)
|
|
expect(result.current.getState().tasks[taskId].progressValue).toBe(50)
|
|
|
|
result.current.getState().setProgressValue(taskId, 100)
|
|
expect(result.current.getState().tasks[taskId].progressValue).toBe(100)
|
|
})
|
|
})
|
|
|
|
it('should compute progress based on completed tasks', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
act(() => {
|
|
const taskId = result.current.getState().create()
|
|
|
|
// Set up task structure
|
|
result.current.getState().setTaskRunning(taskId, [
|
|
{ id: '1', content: 'Task 1', status: 'completed' },
|
|
{ id: '2', content: 'Task 2', status: 'completed' },
|
|
{ id: '3', content: 'Task 3', status: 'running' },
|
|
{ id: '4', content: 'Task 4', status: 'waiting' },
|
|
] as any)
|
|
|
|
result.current.getState().computedProgressValue(taskId)
|
|
|
|
// 2 out of 4 = 50%
|
|
expect(result.current.getState().tasks[taskId].progressValue).toBe(50)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Update Counter', () => {
|
|
it('should increment update count', () => {
|
|
const { result } = renderHook(() => useChatStore())
|
|
|
|
const initialCount = result.current.getState().updateCount
|
|
|
|
act(() => {
|
|
result.current.getState().setUpdateCount()
|
|
})
|
|
|
|
expect(result.current.getState().updateCount).toBe(initialCount + 1)
|
|
|
|
act(() => {
|
|
result.current.getState().setUpdateCount()
|
|
})
|
|
|
|
expect(result.current.getState().updateCount).toBe(initialCount + 2)
|
|
})
|
|
})
|
|
})
|