@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
315 lines (275 loc) • 8.88 kB
text/typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { KanbanDataStore } from '../store.js';
import { ConfigManager } from '../../../config/config-manager.js';
import { Task, KanbanBoard } from '../types.js';
vi.mock('../../../config/config-manager.js');
describe('Kanban Enhanced Features Tests', () => {
let store: KanbanDataStore;
let mockConfigManager: vi.Mocked<ConfigManager>;
let board: KanbanBoard;
beforeEach(async () => {
mockConfigManager = new ConfigManager() as vi.Mocked<ConfigManager>;
mockConfigManager.getStorageManager = vi.fn().mockReturnValue({
loadData: vi.fn().mockResolvedValue(null),
saveData: vi.fn().mockResolvedValue(undefined),
ensureStorageDirectories: vi.fn().mockResolvedValue(undefined)
});
store = new KanbanDataStore(mockConfigManager);
await store.init();
board = store.createBoard('Test Board');
});
describe('Acceptance Criteria Management', () => {
it('should add task with acceptance criteria', () => {
const task = store.addTask('Test Board', {
title: 'Implement feature',
column: 'To Do',
acceptanceCriteria: [
{
id: 'ac-1',
description: 'Feature works correctly',
status: 'pending'
},
{
id: 'ac-2',
description: 'Tests are passing',
status: 'pending'
}
]
});
expect(task).toBeDefined();
expect(task!.acceptanceCriteria).toHaveLength(2);
expect(task!.acceptanceCriteria[0].status).toBe('pending');
});
it('should update acceptance criterion status', () => {
const task = store.addTask('Test Board', {
title: 'Test task',
column: 'To Do',
acceptanceCriteria: [
{
id: 'ac-1',
description: 'Criterion 1',
status: 'pending'
}
]
});
const criterion = task!.acceptanceCriteria[0];
criterion.status = 'completed';
criterion.verifiedBy = 'tester';
criterion.verifiedAt = new Date();
const success = store.updateTask('Test Board', task!.id, {
acceptanceCriteria: [criterion]
});
expect(success).toBe(true);
const updatedTask = board.tasks.find(t => t.id === task!.id);
expect(updatedTask!.acceptanceCriteria[0].status).toBe('completed');
expect(updatedTask!.acceptanceCriteria[0].verifiedBy).toBe('tester');
});
});
describe('Task Dependencies', () => {
it('should add task with dependencies', () => {
const blockerTask = store.addTask('Test Board', {
title: 'Blocker task',
column: 'To Do'
});
const dependentTask = store.addTask('Test Board', {
title: 'Dependent task',
column: 'To Do',
dependencies: [
{
taskId: blockerTask!.id,
type: 'depends_on'
}
]
});
expect(dependentTask!.dependencies).toHaveLength(1);
expect(dependentTask!.dependencies[0].taskId).toBe(blockerTask!.id);
expect(dependentTask!.dependencies[0].type).toBe('depends_on');
});
it('should handle circular dependencies check', () => {
const task1 = store.addTask('Test Board', {
title: 'Task 1',
column: 'To Do'
});
const task2 = store.addTask('Test Board', {
title: 'Task 2',
column: 'To Do',
dependencies: [{
taskId: task1!.id,
type: 'depends_on'
}]
});
// This would need validation logic in the actual implementation
// For now, we just verify the structure is correct
expect(task2!.dependencies[0].taskId).toBe(task1!.id);
});
});
describe('Time Tracking', () => {
it('should add task with time estimate', () => {
const task = store.addTask('Test Board', {
title: 'Timed task',
column: 'To Do',
timeTracking: {
estimated: 8
}
});
expect(task!.timeTracking).toBeDefined();
expect(task!.timeTracking!.estimated).toBe(8);
});
it('should track time spent on task', () => {
const task = store.addTask('Test Board', {
title: 'Task',
column: 'To Do',
timeTracking: {
estimated: 8
}
});
const success = store.updateTask('Test Board', task!.id, {
timeTracking: {
estimated: 8,
spent: 3,
logs: [{
date: new Date(),
hours: 3,
description: 'Initial work'
}]
}
});
expect(success).toBe(true);
const updatedTask = board.tasks.find(t => t.id === task!.id);
expect(updatedTask!.timeTracking!.spent).toBe(3);
expect(updatedTask!.timeTracking!.logs).toHaveLength(1);
});
});
describe('Tags Management', () => {
it('should use tags instead of labels', () => {
const task = store.addTask('Test Board', {
title: 'Tagged task',
column: 'To Do',
tags: ['frontend', 'urgent']
});
expect(task!.tags).toEqual(['frontend', 'urgent']);
});
it('should support legacy labels property', () => {
const task = store.addTask('Test Board', {
title: 'Legacy task',
column: 'To Do',
labels: ['backend', 'bug']
});
// Should convert labels to tags
expect(task!.tags).toEqual(['backend', 'bug']);
});
});
describe('Comments and Collaboration', () => {
it('should add comments to task', () => {
const task = store.addTask('Test Board', {
title: 'Task with comments',
column: 'To Do',
comments: []
});
const comment = {
id: 'comment-1',
author: 'developer',
content: 'Working on this now',
createdAt: new Date()
};
const success = store.updateTask('Test Board', task!.id, {
comments: [comment]
});
expect(success).toBe(true);
const updatedTask = board.tasks.find(t => t.id === task!.id);
expect(updatedTask!.comments).toHaveLength(1);
expect(updatedTask!.comments[0].content).toBe('Working on this now');
});
it('should track watchers on task', () => {
const task = store.addTask('Test Board', {
title: 'Watched task',
column: 'To Do',
watchers: ['user1', 'user2']
});
expect(task!.watchers).toEqual(['user1', 'user2']);
});
});
describe('Subtasks', () => {
it('should add subtasks to main task', () => {
const task = store.addTask('Test Board', {
title: 'Main task',
column: 'To Do',
subtasks: [
{
id: 'subtask-1',
title: 'Setup environment',
completed: false
},
{
id: 'subtask-2',
title: 'Write tests',
completed: false
}
]
});
expect(task!.subtasks).toHaveLength(2);
expect(task!.subtasks[0].title).toBe('Setup environment');
});
it('should update subtask completion', () => {
const task = store.addTask('Test Board', {
title: 'Task',
column: 'To Do',
subtasks: [{
id: 'subtask-1',
title: 'Subtask',
completed: false
}]
});
const success = store.updateTask('Test Board', task!.id, {
subtasks: [{
id: 'subtask-1',
title: 'Subtask',
completed: true,
completedAt: new Date()
}]
});
expect(success).toBe(true);
const updatedTask = board.tasks.find(t => t.id === task!.id);
expect(updatedTask!.subtasks[0].completed).toBe(true);
});
});
describe('Attachments', () => {
it('should add attachments to task', () => {
const task = store.addTask('Test Board', {
title: 'Task with files',
column: 'To Do',
attachments: [
{
id: 'attach-1',
filename: 'design.pdf',
url: '/files/design.pdf',
uploadedBy: 'designer',
uploadedAt: new Date()
}
]
});
expect(task!.attachments).toHaveLength(1);
expect(task!.attachments[0].filename).toBe('design.pdf');
});
});
describe('Custom Fields', () => {
it('should support custom fields', () => {
const task = store.addTask('Test Board', {
title: 'Task with custom data',
column: 'To Do',
customFields: [
{
key: 'client',
value: 'ACME Corp'
},
{
key: 'invoice_number',
value: 'INV-2024-001'
}
]
});
expect(task!.customFields).toHaveLength(2);
expect(task!.customFields.find(f => f.key === 'client')?.value).toBe('ACME Corp');
});
});
});