todoManager.test.ts•4.73 kB
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { TodoManager } from '../src/todoManager.js';
import { readFile, writeFile, unlink, mkdir } from 'fs/promises';
import { join } from 'path';
describe('TodoManager', () => {
let todoManager: TodoManager;
const testDir = '/tmp/todo-test';
const testFile = join(testDir, 'todo.md');
beforeEach(async () => {
// Create test directory
try {
await mkdir(testDir, { recursive: true });
} catch {
// Directory might already exist
}
todoManager = new TodoManager(testDir);
// Clean up test file if it exists
try {
await unlink(testFile);
} catch {
// File doesn't exist, that's fine
}
});
afterEach(async () => {
// Clean up test file
try {
await unlink(testFile);
} catch {
// File doesn't exist, that's fine
}
});
describe('listTodos', () => {
it('should return empty list when no file exists', async () => {
const result = await todoManager.listTodos();
expect(result).toEqual({
todos: [],
total: 0,
completed: 0,
pending: 0,
});
});
it('should parse existing markdown file correctly', async () => {
const content = `# Todo List
- [ ] First todo <!-- id:123 -->
- [x] Second todo <!-- id:456 -->
<!-- Generated by MCP Todo Server -->
`;
await writeFile(testFile, content, 'utf-8');
const result = await todoManager.listTodos();
expect(result.total).toBe(2);
expect(result.completed).toBe(1);
expect(result.pending).toBe(1);
expect(result.todos[0]?.text).toBe('First todo');
expect(result.todos[0]?.completed).toBe(false);
expect(result.todos[1]?.text).toBe('Second todo');
expect(result.todos[1]?.completed).toBe(true);
});
});
describe('addTodo', () => {
it('should add a new todo to empty file', async () => {
const todo = await todoManager.addTodo({ text: 'New task' });
expect(todo.text).toBe('New task');
expect(todo.completed).toBe(false);
expect(todo.id).toBeDefined();
const content = await readFile(testFile, 'utf-8');
expect(content).toContain('- [ ] New task');
expect(content).toContain(`id:${todo.id}`);
});
it('should add todo to existing file', async () => {
await todoManager.addTodo({ text: 'First task' });
await todoManager.addTodo({ text: 'Second task' });
const result = await todoManager.listTodos();
expect(result.total).toBe(2);
expect(result.todos[0]?.text).toBe('First task');
expect(result.todos[1]?.text).toBe('Second task');
});
});
describe('updateTodo', () => {
it('should update todo text', async () => {
const todo = await todoManager.addTodo({ text: 'Original text' });
const updated = await todoManager.updateTodo({
id: todo.id,
text: 'Updated text',
});
expect(updated.text).toBe('Updated text');
expect(updated.id).toBe(todo.id);
});
it('should mark todo as completed', async () => {
const todo = await todoManager.addTodo({ text: 'Task to complete' });
const updated = await todoManager.updateTodo({
id: todo.id,
completed: true,
});
expect(updated.completed).toBe(true);
expect(updated.completedAt).toBeDefined();
});
it('should throw error for non-existent todo', async () => {
await expect(
todoManager.updateTodo({ id: 'non-existent', text: 'New text' })
).rejects.toThrow('Todo with id non-existent not found');
});
});
describe('deleteTodo', () => {
it('should delete existing todo', async () => {
const todo = await todoManager.addTodo({ text: 'To be deleted' });
await todoManager.deleteTodo({ id: todo.id });
const result = await todoManager.listTodos();
expect(result.total).toBe(0);
});
it('should throw error for non-existent todo', async () => {
await expect(
todoManager.deleteTodo({ id: 'non-existent' })
).rejects.toThrow('Todo with id non-existent not found');
});
});
describe('clearCompleted', () => {
it('should remove all completed todos', async () => {
const todo1 = await todoManager.addTodo({ text: 'Task 1' });
const todo2 = await todoManager.addTodo({ text: 'Task 2' });
await todoManager.updateTodo({ id: todo1.id, completed: true });
const cleared = await todoManager.clearCompleted();
expect(cleared).toBe(1);
const result = await todoManager.listTodos();
expect(result.total).toBe(1);
expect(result.todos[0]?.text).toBe('Task 2');
});
});
});