mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2025-07-03 23:19:44 +00:00
339 lines
9.4 KiB
JavaScript
339 lines
9.4 KiB
JavaScript
![]() |
/**
|
||
|
* Tests for the generate-task-files.js module
|
||
|
*/
|
||
|
import { jest } from '@jest/globals';
|
||
|
|
||
|
// Mock the dependencies before importing the module under test
|
||
|
jest.unstable_mockModule('fs', () => ({
|
||
|
default: {
|
||
|
existsSync: jest.fn(),
|
||
|
mkdirSync: jest.fn(),
|
||
|
readdirSync: jest.fn(),
|
||
|
unlinkSync: jest.fn(),
|
||
|
writeFileSync: jest.fn()
|
||
|
},
|
||
|
existsSync: jest.fn(),
|
||
|
mkdirSync: jest.fn(),
|
||
|
readdirSync: jest.fn(),
|
||
|
unlinkSync: jest.fn(),
|
||
|
writeFileSync: jest.fn()
|
||
|
}));
|
||
|
|
||
|
jest.unstable_mockModule('path', () => ({
|
||
|
default: {
|
||
|
join: jest.fn((...args) => args.join('/')),
|
||
|
dirname: jest.fn((p) => p.split('/').slice(0, -1).join('/'))
|
||
|
},
|
||
|
join: jest.fn((...args) => args.join('/')),
|
||
|
dirname: jest.fn((p) => p.split('/').slice(0, -1).join('/'))
|
||
|
}));
|
||
|
|
||
|
jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
|
||
|
readJSON: jest.fn(),
|
||
|
writeJSON: jest.fn(),
|
||
|
log: jest.fn(),
|
||
|
CONFIG: {
|
||
|
model: 'mock-claude-model',
|
||
|
maxTokens: 4000,
|
||
|
temperature: 0.7,
|
||
|
debug: false
|
||
|
},
|
||
|
sanitizePrompt: jest.fn((prompt) => prompt),
|
||
|
truncate: jest.fn((text) => text),
|
||
|
isSilentMode: jest.fn(() => false),
|
||
|
findTaskById: jest.fn((tasks, id) =>
|
||
|
tasks.find((t) => t.id === parseInt(id))
|
||
|
),
|
||
|
findProjectRoot: jest.fn(() => '/mock/project/root'),
|
||
|
resolveEnvVariable: jest.fn((varName) => `mock_${varName}`)
|
||
|
}));
|
||
|
|
||
|
jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
|
||
|
formatDependenciesWithStatus: jest.fn(),
|
||
|
displayBanner: jest.fn(),
|
||
|
displayTaskList: jest.fn(),
|
||
|
startLoadingIndicator: jest.fn(() => ({ stop: jest.fn() })),
|
||
|
stopLoadingIndicator: jest.fn(),
|
||
|
createProgressBar: jest.fn(() => ' MOCK_PROGRESS_BAR '),
|
||
|
getStatusWithColor: jest.fn((status) => status),
|
||
|
getComplexityWithColor: jest.fn((score) => `Score: ${score}`)
|
||
|
}));
|
||
|
|
||
|
jest.unstable_mockModule(
|
||
|
'../../../../../scripts/modules/dependency-manager.js',
|
||
|
() => ({
|
||
|
validateAndFixDependencies: jest.fn(),
|
||
|
validateTaskDependencies: jest.fn()
|
||
|
})
|
||
|
);
|
||
|
|
||
|
jest.unstable_mockModule(
|
||
|
'../../../../../scripts/modules/config-manager.js',
|
||
|
() => ({
|
||
|
getDebugFlag: jest.fn(() => false),
|
||
|
getProjectName: jest.fn(() => 'Test Project')
|
||
|
})
|
||
|
);
|
||
|
|
||
|
// Import the mocked modules
|
||
|
const { readJSON, writeJSON, log, findProjectRoot } = await import(
|
||
|
'../../../../../scripts/modules/utils.js'
|
||
|
);
|
||
|
const { formatDependenciesWithStatus } = await import(
|
||
|
'../../../../../scripts/modules/ui.js'
|
||
|
);
|
||
|
const { validateAndFixDependencies } = await import(
|
||
|
'../../../../../scripts/modules/dependency-manager.js'
|
||
|
);
|
||
|
|
||
|
const fs = (await import('fs')).default;
|
||
|
const path = (await import('path')).default;
|
||
|
|
||
|
// Import the module under test
|
||
|
const { default: generateTaskFiles } = await import(
|
||
|
'../../../../../scripts/modules/task-manager/generate-task-files.js'
|
||
|
);
|
||
|
|
||
|
describe('generateTaskFiles', () => {
|
||
|
// Sample task data for testing
|
||
|
const sampleTasks = {
|
||
|
meta: { projectName: 'Test Project' },
|
||
|
tasks: [
|
||
|
{
|
||
|
id: 1,
|
||
|
title: 'Task 1',
|
||
|
description: 'First task description',
|
||
|
status: 'pending',
|
||
|
dependencies: [],
|
||
|
priority: 'high',
|
||
|
details: 'Detailed information for task 1',
|
||
|
testStrategy: 'Test strategy for task 1'
|
||
|
},
|
||
|
{
|
||
|
id: 2,
|
||
|
title: 'Task 2',
|
||
|
description: 'Second task description',
|
||
|
status: 'pending',
|
||
|
dependencies: [1],
|
||
|
priority: 'medium',
|
||
|
details: 'Detailed information for task 2',
|
||
|
testStrategy: 'Test strategy for task 2'
|
||
|
},
|
||
|
{
|
||
|
id: 3,
|
||
|
title: 'Task with Subtasks',
|
||
|
description: 'Task with subtasks description',
|
||
|
status: 'pending',
|
||
|
dependencies: [1, 2],
|
||
|
priority: 'high',
|
||
|
details: 'Detailed information for task 3',
|
||
|
testStrategy: 'Test strategy for task 3',
|
||
|
subtasks: [
|
||
|
{
|
||
|
id: 1,
|
||
|
title: 'Subtask 1',
|
||
|
description: 'First subtask',
|
||
|
status: 'pending',
|
||
|
dependencies: [],
|
||
|
details: 'Details for subtask 1'
|
||
|
},
|
||
|
{
|
||
|
id: 2,
|
||
|
title: 'Subtask 2',
|
||
|
description: 'Second subtask',
|
||
|
status: 'pending',
|
||
|
dependencies: [1],
|
||
|
details: 'Details for subtask 2'
|
||
|
}
|
||
|
]
|
||
|
}
|
||
|
]
|
||
|
};
|
||
|
|
||
|
beforeEach(() => {
|
||
|
jest.clearAllMocks();
|
||
|
});
|
||
|
|
||
|
test('should generate task files from tasks.json - working test', async () => {
|
||
|
// Set up mocks for this specific test
|
||
|
readJSON.mockImplementationOnce(() => sampleTasks);
|
||
|
fs.existsSync.mockImplementationOnce(() => true);
|
||
|
|
||
|
// Call the function
|
||
|
const tasksPath = 'tasks/tasks.json';
|
||
|
const outputDir = 'tasks';
|
||
|
|
||
|
await generateTaskFiles(tasksPath, outputDir, {
|
||
|
mcpLog: { info: jest.fn() }
|
||
|
});
|
||
|
|
||
|
// Verify the data was read
|
||
|
expect(readJSON).toHaveBeenCalledWith(tasksPath);
|
||
|
|
||
|
// Verify dependencies were validated
|
||
|
expect(validateAndFixDependencies).toHaveBeenCalledWith(
|
||
|
sampleTasks,
|
||
|
tasksPath
|
||
|
);
|
||
|
|
||
|
// Verify files were written for each task
|
||
|
expect(fs.writeFileSync).toHaveBeenCalledTimes(3);
|
||
|
|
||
|
// Verify specific file paths
|
||
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||
|
'tasks/task_001.txt',
|
||
|
expect.any(String)
|
||
|
);
|
||
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||
|
'tasks/task_002.txt',
|
||
|
expect.any(String)
|
||
|
);
|
||
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||
|
'tasks/task_003.txt',
|
||
|
expect.any(String)
|
||
|
);
|
||
|
});
|
||
|
|
||
|
test('should format dependencies with status indicators', async () => {
|
||
|
// Set up mocks
|
||
|
readJSON.mockImplementationOnce(() => sampleTasks);
|
||
|
fs.existsSync.mockImplementationOnce(() => true);
|
||
|
formatDependenciesWithStatus.mockReturnValue(
|
||
|
'✅ Task 1 (done), ⏱️ Task 2 (pending)'
|
||
|
);
|
||
|
|
||
|
// Call the function
|
||
|
await generateTaskFiles('tasks/tasks.json', 'tasks', {
|
||
|
mcpLog: { info: jest.fn() }
|
||
|
});
|
||
|
|
||
|
// Verify formatDependenciesWithStatus was called for tasks with dependencies
|
||
|
expect(formatDependenciesWithStatus).toHaveBeenCalled();
|
||
|
});
|
||
|
|
||
|
test('should handle tasks with no subtasks', async () => {
|
||
|
// Create data with tasks that have no subtasks
|
||
|
const tasksWithoutSubtasks = {
|
||
|
meta: { projectName: 'Test Project' },
|
||
|
tasks: [
|
||
|
{
|
||
|
id: 1,
|
||
|
title: 'Simple Task',
|
||
|
description: 'A simple task without subtasks',
|
||
|
status: 'pending',
|
||
|
dependencies: [],
|
||
|
priority: 'medium',
|
||
|
details: 'Simple task details',
|
||
|
testStrategy: 'Simple test strategy'
|
||
|
}
|
||
|
]
|
||
|
};
|
||
|
|
||
|
readJSON.mockImplementationOnce(() => tasksWithoutSubtasks);
|
||
|
fs.existsSync.mockImplementationOnce(() => true);
|
||
|
|
||
|
// Call the function
|
||
|
await generateTaskFiles('tasks/tasks.json', 'tasks', {
|
||
|
mcpLog: { info: jest.fn() }
|
||
|
});
|
||
|
|
||
|
// Verify the file was written
|
||
|
expect(fs.writeFileSync).toHaveBeenCalledTimes(1);
|
||
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||
|
'tasks/task_001.txt',
|
||
|
expect.any(String)
|
||
|
);
|
||
|
});
|
||
|
|
||
|
test("should create the output directory if it doesn't exist", async () => {
|
||
|
// Set up mocks
|
||
|
readJSON.mockImplementationOnce(() => sampleTasks);
|
||
|
fs.existsSync.mockImplementation((path) => {
|
||
|
if (path === 'tasks') return false; // Directory doesn't exist
|
||
|
return true; // Other paths exist
|
||
|
});
|
||
|
|
||
|
// Call the function
|
||
|
await generateTaskFiles('tasks/tasks.json', 'tasks', {
|
||
|
mcpLog: { info: jest.fn() }
|
||
|
});
|
||
|
|
||
|
// Verify mkdir was called
|
||
|
expect(fs.mkdirSync).toHaveBeenCalledWith('tasks', { recursive: true });
|
||
|
});
|
||
|
|
||
|
test('should format task files with proper sections', async () => {
|
||
|
// Set up mocks
|
||
|
readJSON.mockImplementationOnce(() => sampleTasks);
|
||
|
fs.existsSync.mockImplementationOnce(() => true);
|
||
|
|
||
|
// Call the function
|
||
|
await generateTaskFiles('tasks/tasks.json', 'tasks', {
|
||
|
mcpLog: { info: jest.fn() }
|
||
|
});
|
||
|
|
||
|
// Get the content written to the first task file
|
||
|
const firstTaskContent = fs.writeFileSync.mock.calls[0][1];
|
||
|
|
||
|
// Verify the content includes expected sections
|
||
|
expect(firstTaskContent).toContain('# Task ID: 1');
|
||
|
expect(firstTaskContent).toContain('# Title: Task 1');
|
||
|
expect(firstTaskContent).toContain('# Description');
|
||
|
expect(firstTaskContent).toContain('# Status');
|
||
|
expect(firstTaskContent).toContain('# Priority');
|
||
|
expect(firstTaskContent).toContain('# Dependencies');
|
||
|
expect(firstTaskContent).toContain('# Details:');
|
||
|
expect(firstTaskContent).toContain('# Test Strategy:');
|
||
|
});
|
||
|
|
||
|
test('should include subtasks in task files when present', async () => {
|
||
|
// Set up mocks
|
||
|
readJSON.mockImplementationOnce(() => sampleTasks);
|
||
|
fs.existsSync.mockImplementationOnce(() => true);
|
||
|
|
||
|
// Call the function
|
||
|
await generateTaskFiles('tasks/tasks.json', 'tasks', {
|
||
|
mcpLog: { info: jest.fn() }
|
||
|
});
|
||
|
|
||
|
// Get the content written to the task file with subtasks (task 3)
|
||
|
const taskWithSubtasksContent = fs.writeFileSync.mock.calls[2][1];
|
||
|
|
||
|
// Verify the content includes subtasks section
|
||
|
expect(taskWithSubtasksContent).toContain('# Subtasks:');
|
||
|
expect(taskWithSubtasksContent).toContain('## 1. Subtask 1');
|
||
|
expect(taskWithSubtasksContent).toContain('## 2. Subtask 2');
|
||
|
});
|
||
|
|
||
|
test('should handle errors during file generation', () => {
|
||
|
// Mock an error in readJSON
|
||
|
readJSON.mockImplementationOnce(() => {
|
||
|
throw new Error('File read failed');
|
||
|
});
|
||
|
|
||
|
// Call the function and expect it to handle the error
|
||
|
expect(() => {
|
||
|
generateTaskFiles('tasks/tasks.json', 'tasks', {
|
||
|
mcpLog: { info: jest.fn() }
|
||
|
});
|
||
|
}).toThrow('File read failed');
|
||
|
});
|
||
|
|
||
|
test('should validate dependencies before generating files', async () => {
|
||
|
// Set up mocks
|
||
|
readJSON.mockImplementationOnce(() => sampleTasks);
|
||
|
fs.existsSync.mockImplementationOnce(() => true);
|
||
|
|
||
|
// Call the function
|
||
|
await generateTaskFiles('tasks/tasks.json', 'tasks', {
|
||
|
mcpLog: { info: jest.fn() }
|
||
|
});
|
||
|
|
||
|
// Verify validateAndFixDependencies was called
|
||
|
expect(validateAndFixDependencies).toHaveBeenCalledWith(
|
||
|
sampleTasks,
|
||
|
'tasks/tasks.json'
|
||
|
);
|
||
|
});
|
||
|
});
|