Files
ai-terminal-assistant/tests/unit/tools/todo-manager.test.ts
T
kurihada 729fb2d42a feat: 添加完整的单元测试套件
- 新增 vitest 测试框架配置
- 添加 54 个测试文件,共 951 个测试用例
- 覆盖核心模块:
  - Agent: executor, registry, config-loader, permission-merger
  - Context: manager, compaction, prune, token-counter
  - Permission: manager, bash/file/git/web checkers, wildcard
  - Session: manager, storage
  - Tools: filesystem (12个), git (10个), web, shell, todo, task
  - LSP: client, server, language
  - Utils: config, diff
  - UI: terminal
2025-12-11 14:45:24 +08:00

334 lines
9.3 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest';
import type { Todo, TodoStatus } from '../../../src/session/types.js';
import type { SessionManager } from '../../../src/session/index.js';
// 需要单独导入 todoManager 因为它是单例
// 每个测试创建新的实例来避免状态污染
// 简单的 TodoManager 测试类(复制逻辑以测试)
class TestTodoManager {
private sessionManager: SessionManager | null = null;
setSessionManager(manager: SessionManager): void {
this.sessionManager = manager;
}
getTodos(): Todo[] {
if (!this.sessionManager) {
return [];
}
return this.sessionManager.getTodos();
}
async setTodos(todos: Todo[]): Promise<void> {
if (!this.sessionManager) {
return;
}
await this.sessionManager.setTodos(todos);
}
async addTodo(content: string, status: TodoStatus = 'pending'): Promise<Todo> {
const todos = this.getTodos();
const now = new Date().toISOString();
const newTodo: Todo = {
id: this.generateId(),
content,
status,
createdAt: now,
updatedAt: now,
};
todos.push(newTodo);
await this.setTodos(todos);
return newTodo;
}
async updateTodoStatus(id: string, status: TodoStatus): Promise<boolean> {
const todos = this.getTodos();
const todo = todos.find((t) => t.id === id);
if (!todo) return false;
todo.status = status;
todo.updatedAt = new Date().toISOString();
await this.setTodos(todos);
return true;
}
async deleteTodo(id: string): Promise<boolean> {
const todos = this.getTodos();
const index = todos.findIndex((t) => t.id === id);
if (index === -1) return false;
todos.splice(index, 1);
await this.setTodos(todos);
return true;
}
async clearTodos(): Promise<void> {
await this.setTodos([]);
}
private generateId(): string {
return Math.random().toString(36).substring(2, 10);
}
isInitialized(): boolean {
return this.sessionManager !== null;
}
}
// Mock SessionManager
function createMockSessionManager(): SessionManager {
let todos: Todo[] = [];
return {
getTodos: vi.fn(() => [...todos]),
setTodos: vi.fn(async (newTodos: Todo[]) => {
todos = [...newTodos];
}),
} as unknown as SessionManager;
}
describe('TodoManager - Todo 管理器', () => {
let todoManager: TestTodoManager;
let mockSessionManager: SessionManager;
beforeEach(() => {
todoManager = new TestTodoManager();
mockSessionManager = createMockSessionManager();
});
describe('初始化状态', () => {
it('未设置 sessionManager 时 isInitialized 返回 false', () => {
expect(todoManager.isInitialized()).toBe(false);
});
it('设置 sessionManager 后 isInitialized 返回 true', () => {
todoManager.setSessionManager(mockSessionManager);
expect(todoManager.isInitialized()).toBe(true);
});
it('未初始化时 getTodos 返回空数组', () => {
expect(todoManager.getTodos()).toEqual([]);
});
it('未初始化时 setTodos 不报错', async () => {
await expect(todoManager.setTodos([{
id: '1',
content: 'test',
status: 'pending',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}])).resolves.not.toThrow();
});
});
describe('getTodos', () => {
beforeEach(() => {
todoManager.setSessionManager(mockSessionManager);
});
it('返回空数组当没有 todos', () => {
const todos = todoManager.getTodos();
expect(todos).toEqual([]);
});
it('返回 sessionManager 中的 todos', async () => {
await todoManager.addTodo('Task 1');
await todoManager.addTodo('Task 2');
const todos = todoManager.getTodos();
expect(todos).toHaveLength(2);
});
});
describe('setTodos', () => {
beforeEach(() => {
todoManager.setSessionManager(mockSessionManager);
});
it('设置 todos 列表', async () => {
const todos: Todo[] = [
{
id: '1',
content: 'Task 1',
status: 'pending',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
];
await todoManager.setTodos(todos);
expect(todoManager.getTodos()).toHaveLength(1);
expect(todoManager.getTodos()[0].content).toBe('Task 1');
});
it('清空 todos', async () => {
await todoManager.addTodo('Task 1');
await todoManager.setTodos([]);
expect(todoManager.getTodos()).toHaveLength(0);
});
});
describe('addTodo', () => {
beforeEach(() => {
todoManager.setSessionManager(mockSessionManager);
});
it('添加新 todo 默认状态为 pending', async () => {
const todo = await todoManager.addTodo('New task');
expect(todo.content).toBe('New task');
expect(todo.status).toBe('pending');
expect(todo.id).toBeDefined();
expect(todo.createdAt).toBeDefined();
expect(todo.updatedAt).toBeDefined();
});
it('添加新 todo 指定状态', async () => {
const todo = await todoManager.addTodo('In progress task', 'in_progress');
expect(todo.status).toBe('in_progress');
});
it('添加的 todo 出现在列表中', async () => {
await todoManager.addTodo('Task 1');
await todoManager.addTodo('Task 2');
const todos = todoManager.getTodos();
expect(todos).toHaveLength(2);
expect(todos.some(t => t.content === 'Task 1')).toBe(true);
expect(todos.some(t => t.content === 'Task 2')).toBe(true);
});
it('每个 todo 有唯一 ID', async () => {
const todo1 = await todoManager.addTodo('Task 1');
const todo2 = await todoManager.addTodo('Task 2');
expect(todo1.id).not.toBe(todo2.id);
});
});
describe('updateTodoStatus', () => {
beforeEach(() => {
todoManager.setSessionManager(mockSessionManager);
});
it('更新存在的 todo 状态', async () => {
const todo = await todoManager.addTodo('Task');
const result = await todoManager.updateTodoStatus(todo.id, 'completed');
expect(result).toBe(true);
const updated = todoManager.getTodos().find(t => t.id === todo.id);
expect(updated?.status).toBe('completed');
});
it('更新不存在的 todo 返回 false', async () => {
const result = await todoManager.updateTodoStatus('non-existent-id', 'completed');
expect(result).toBe(false);
});
it('更新状态时更新 updatedAt', async () => {
const todo = await todoManager.addTodo('Task');
const originalUpdatedAt = todo.updatedAt;
// 等待一小段时间确保时间戳不同
await new Promise(resolve => setTimeout(resolve, 10));
await todoManager.updateTodoStatus(todo.id, 'in_progress');
const updated = todoManager.getTodos().find(t => t.id === todo.id);
expect(updated?.updatedAt).not.toBe(originalUpdatedAt);
});
});
describe('deleteTodo', () => {
beforeEach(() => {
todoManager.setSessionManager(mockSessionManager);
});
it('删除存在的 todo', async () => {
const todo = await todoManager.addTodo('Task to delete');
const result = await todoManager.deleteTodo(todo.id);
expect(result).toBe(true);
expect(todoManager.getTodos().find(t => t.id === todo.id)).toBeUndefined();
});
it('删除不存在的 todo 返回 false', async () => {
const result = await todoManager.deleteTodo('non-existent-id');
expect(result).toBe(false);
});
it('删除后列表长度减少', async () => {
await todoManager.addTodo('Task 1');
const todo2 = await todoManager.addTodo('Task 2');
await todoManager.addTodo('Task 3');
await todoManager.deleteTodo(todo2.id);
expect(todoManager.getTodos()).toHaveLength(2);
});
});
describe('clearTodos', () => {
beforeEach(() => {
todoManager.setSessionManager(mockSessionManager);
});
it('清空所有 todos', async () => {
await todoManager.addTodo('Task 1');
await todoManager.addTodo('Task 2');
await todoManager.addTodo('Task 3');
await todoManager.clearTodos();
expect(todoManager.getTodos()).toHaveLength(0);
});
it('清空空列表不报错', async () => {
await expect(todoManager.clearTodos()).resolves.not.toThrow();
});
});
});
describe('Todo 状态流转', () => {
let todoManager: TestTodoManager;
let mockSessionManager: SessionManager;
beforeEach(() => {
todoManager = new TestTodoManager();
mockSessionManager = createMockSessionManager();
todoManager.setSessionManager(mockSessionManager);
});
it('pending -> in_progress -> completed', async () => {
const todo = await todoManager.addTodo('Task');
expect(todo.status).toBe('pending');
await todoManager.updateTodoStatus(todo.id, 'in_progress');
let updated = todoManager.getTodos().find(t => t.id === todo.id);
expect(updated?.status).toBe('in_progress');
await todoManager.updateTodoStatus(todo.id, 'completed');
updated = todoManager.getTodos().find(t => t.id === todo.id);
expect(updated?.status).toBe('completed');
});
it('直接从 pending 到 completed', async () => {
const todo = await todoManager.addTodo('Quick task');
await todoManager.updateTodoStatus(todo.id, 'completed');
const updated = todoManager.getTodos().find(t => t.id === todo.id);
expect(updated?.status).toBe('completed');
});
});