mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2025-07-03 23:19:44 +00:00
270 lines
6.2 KiB
JavaScript
270 lines
6.2 KiB
JavaScript
![]() |
/**
|
||
|
* Tests for the clear-subtasks.js module
|
||
|
*/
|
||
|
import { jest } from '@jest/globals';
|
||
|
|
||
|
// Mock the dependencies before importing the module under test
|
||
|
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
|
||
|
},
|
||
|
findTaskById: jest.fn(),
|
||
|
isSilentMode: jest.fn(() => false),
|
||
|
truncate: jest.fn((text) => text)
|
||
|
}));
|
||
|
|
||
|
jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
|
||
|
displayBanner: jest.fn()
|
||
|
}));
|
||
|
|
||
|
jest.unstable_mockModule(
|
||
|
'../../../../../scripts/modules/task-manager/generate-task-files.js',
|
||
|
() => ({
|
||
|
default: jest.fn().mockResolvedValue()
|
||
|
})
|
||
|
);
|
||
|
|
||
|
// Mock external UI libraries
|
||
|
jest.unstable_mockModule('chalk', () => ({
|
||
|
default: {
|
||
|
white: {
|
||
|
bold: jest.fn((text) => text)
|
||
|
},
|
||
|
cyan: Object.assign(
|
||
|
jest.fn((text) => text),
|
||
|
{
|
||
|
bold: jest.fn((text) => text)
|
||
|
}
|
||
|
),
|
||
|
green: jest.fn((text) => text),
|
||
|
yellow: jest.fn((text) => text),
|
||
|
bold: jest.fn((text) => text)
|
||
|
}
|
||
|
}));
|
||
|
|
||
|
jest.unstable_mockModule('boxen', () => ({
|
||
|
default: jest.fn((text) => text)
|
||
|
}));
|
||
|
|
||
|
jest.unstable_mockModule('cli-table3', () => ({
|
||
|
default: jest.fn().mockImplementation(() => ({
|
||
|
push: jest.fn(),
|
||
|
toString: jest.fn(() => 'mocked table')
|
||
|
}))
|
||
|
}));
|
||
|
|
||
|
// Import the mocked modules
|
||
|
const { readJSON, writeJSON, log } = await import(
|
||
|
'../../../../../scripts/modules/utils.js'
|
||
|
);
|
||
|
|
||
|
const generateTaskFiles = await import(
|
||
|
'../../../../../scripts/modules/task-manager/generate-task-files.js'
|
||
|
);
|
||
|
|
||
|
// Import the module under test
|
||
|
const { default: clearSubtasks } = await import(
|
||
|
'../../../../../scripts/modules/task-manager/clear-subtasks.js'
|
||
|
);
|
||
|
|
||
|
describe('clearSubtasks', () => {
|
||
|
const sampleTasks = {
|
||
|
tasks: [
|
||
|
{
|
||
|
id: 1,
|
||
|
title: 'Task 1',
|
||
|
description: 'First task',
|
||
|
status: 'pending',
|
||
|
dependencies: []
|
||
|
},
|
||
|
{
|
||
|
id: 2,
|
||
|
title: 'Task 2',
|
||
|
description: 'Second task',
|
||
|
status: 'pending',
|
||
|
dependencies: [],
|
||
|
subtasks: [
|
||
|
{
|
||
|
id: 1,
|
||
|
title: 'Subtask 2.1',
|
||
|
description: 'First subtask of task 2',
|
||
|
status: 'pending',
|
||
|
dependencies: []
|
||
|
}
|
||
|
]
|
||
|
},
|
||
|
{
|
||
|
id: 3,
|
||
|
title: 'Task 3',
|
||
|
description: 'Third task',
|
||
|
status: 'pending',
|
||
|
dependencies: [],
|
||
|
subtasks: [
|
||
|
{
|
||
|
id: 1,
|
||
|
title: 'Subtask 3.1',
|
||
|
description: 'First subtask of task 3',
|
||
|
status: 'pending',
|
||
|
dependencies: []
|
||
|
},
|
||
|
{
|
||
|
id: 2,
|
||
|
title: 'Subtask 3.2',
|
||
|
description: 'Second subtask of task 3',
|
||
|
status: 'done',
|
||
|
dependencies: []
|
||
|
}
|
||
|
]
|
||
|
}
|
||
|
]
|
||
|
};
|
||
|
|
||
|
beforeEach(() => {
|
||
|
jest.clearAllMocks();
|
||
|
readJSON.mockReturnValue(JSON.parse(JSON.stringify(sampleTasks)));
|
||
|
|
||
|
// Mock process.exit since this function doesn't have MCP mode support
|
||
|
jest.spyOn(process, 'exit').mockImplementation(() => {
|
||
|
throw new Error('process.exit called');
|
||
|
});
|
||
|
|
||
|
// Mock console.log to avoid output during tests
|
||
|
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||
|
});
|
||
|
|
||
|
afterEach(() => {
|
||
|
// Restore process.exit
|
||
|
process.exit.mockRestore();
|
||
|
console.log.mockRestore();
|
||
|
});
|
||
|
|
||
|
test('should clear subtasks from a specific task', () => {
|
||
|
// Act
|
||
|
clearSubtasks('tasks/tasks.json', '3');
|
||
|
|
||
|
// Assert
|
||
|
expect(readJSON).toHaveBeenCalledWith('tasks/tasks.json');
|
||
|
expect(writeJSON).toHaveBeenCalledWith(
|
||
|
'tasks/tasks.json',
|
||
|
expect.objectContaining({
|
||
|
tasks: expect.arrayContaining([
|
||
|
expect.objectContaining({
|
||
|
id: 3,
|
||
|
subtasks: []
|
||
|
})
|
||
|
])
|
||
|
})
|
||
|
);
|
||
|
expect(generateTaskFiles.default).toHaveBeenCalled();
|
||
|
});
|
||
|
|
||
|
test('should clear subtasks from multiple tasks when given comma-separated IDs', () => {
|
||
|
// Act
|
||
|
clearSubtasks('tasks/tasks.json', '2,3');
|
||
|
|
||
|
// Assert
|
||
|
expect(readJSON).toHaveBeenCalledWith('tasks/tasks.json');
|
||
|
expect(writeJSON).toHaveBeenCalledWith(
|
||
|
'tasks/tasks.json',
|
||
|
expect.objectContaining({
|
||
|
tasks: expect.arrayContaining([
|
||
|
expect.objectContaining({
|
||
|
id: 2,
|
||
|
subtasks: []
|
||
|
}),
|
||
|
expect.objectContaining({
|
||
|
id: 3,
|
||
|
subtasks: []
|
||
|
})
|
||
|
])
|
||
|
})
|
||
|
);
|
||
|
expect(generateTaskFiles.default).toHaveBeenCalled();
|
||
|
});
|
||
|
|
||
|
test('should handle tasks with no subtasks', () => {
|
||
|
// Act
|
||
|
clearSubtasks('tasks/tasks.json', '1');
|
||
|
|
||
|
// Assert
|
||
|
expect(readJSON).toHaveBeenCalledWith('tasks/tasks.json');
|
||
|
// Should not write the file if no changes were made
|
||
|
expect(writeJSON).not.toHaveBeenCalled();
|
||
|
expect(generateTaskFiles.default).not.toHaveBeenCalled();
|
||
|
});
|
||
|
|
||
|
test('should handle non-existent task IDs gracefully', () => {
|
||
|
// Act
|
||
|
clearSubtasks('tasks/tasks.json', '99');
|
||
|
|
||
|
// Assert
|
||
|
expect(readJSON).toHaveBeenCalledWith('tasks/tasks.json');
|
||
|
expect(log).toHaveBeenCalledWith('error', 'Task 99 not found');
|
||
|
// Should not write the file if no changes were made
|
||
|
expect(writeJSON).not.toHaveBeenCalled();
|
||
|
});
|
||
|
|
||
|
test('should handle multiple task IDs including both valid and non-existent IDs', () => {
|
||
|
// Act
|
||
|
clearSubtasks('tasks/tasks.json', '3,99');
|
||
|
|
||
|
// Assert
|
||
|
expect(readJSON).toHaveBeenCalledWith('tasks/tasks.json');
|
||
|
expect(log).toHaveBeenCalledWith('error', 'Task 99 not found');
|
||
|
expect(writeJSON).toHaveBeenCalledWith(
|
||
|
'tasks/tasks.json',
|
||
|
expect.objectContaining({
|
||
|
tasks: expect.arrayContaining([
|
||
|
expect.objectContaining({
|
||
|
id: 3,
|
||
|
subtasks: []
|
||
|
})
|
||
|
])
|
||
|
})
|
||
|
);
|
||
|
expect(generateTaskFiles.default).toHaveBeenCalled();
|
||
|
});
|
||
|
|
||
|
test('should handle file read errors', () => {
|
||
|
// Arrange
|
||
|
readJSON.mockImplementation(() => {
|
||
|
throw new Error('File read failed');
|
||
|
});
|
||
|
|
||
|
// Act & Assert
|
||
|
expect(() => {
|
||
|
clearSubtasks('tasks/tasks.json', '3');
|
||
|
}).toThrow('File read failed');
|
||
|
});
|
||
|
|
||
|
test('should handle invalid tasks data', () => {
|
||
|
// Arrange
|
||
|
readJSON.mockReturnValue(null);
|
||
|
|
||
|
// Act & Assert
|
||
|
expect(() => {
|
||
|
clearSubtasks('tasks/tasks.json', '3');
|
||
|
}).toThrow('process.exit called');
|
||
|
|
||
|
expect(log).toHaveBeenCalledWith('error', 'No valid tasks found.');
|
||
|
});
|
||
|
|
||
|
test('should handle file write errors', () => {
|
||
|
// Arrange
|
||
|
writeJSON.mockImplementation(() => {
|
||
|
throw new Error('File write failed');
|
||
|
});
|
||
|
|
||
|
// Act & Assert
|
||
|
expect(() => {
|
||
|
clearSubtasks('tasks/tasks.json', '3');
|
||
|
}).toThrow('File write failed');
|
||
|
});
|
||
|
});
|