mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2026-01-29 22:02:04 +00:00
123 lines
3.6 KiB
JavaScript
123 lines
3.6 KiB
JavaScript
import { jest } from '@jest/globals';
|
||
|
||
// --- Mock dependencies BEFORE module import ---
|
||
jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
|
||
readJSON: jest.fn(),
|
||
writeJSON: jest.fn(),
|
||
log: jest.fn(),
|
||
CONFIG: {
|
||
model: 'mock-model',
|
||
maxTokens: 4000,
|
||
temperature: 0.7,
|
||
debug: false
|
||
},
|
||
findTaskById: jest.fn(),
|
||
truncate: jest.fn((t) => t),
|
||
isSilentMode: jest.fn(() => false)
|
||
}));
|
||
|
||
// fs is used for file deletion side-effects – stub the methods we touch
|
||
jest.unstable_mockModule('fs', () => ({
|
||
existsSync: jest.fn(() => true),
|
||
unlinkSync: jest.fn()
|
||
}));
|
||
|
||
// path is fine to keep as real since only join/dirname used – no side effects
|
||
|
||
// Import mocked modules
|
||
const { readJSON, writeJSON, log } = await import(
|
||
'../../../../../scripts/modules/utils.js'
|
||
);
|
||
const fs = await import('fs');
|
||
|
||
// Import module under test (AFTER mocks in place)
|
||
const { default: removeTask } = await import(
|
||
'../../../../../scripts/modules/task-manager/remove-task.js'
|
||
);
|
||
|
||
// ---- Test data helpers ----
|
||
const buildSampleTaggedTasks = () => ({
|
||
master: {
|
||
tasks: [
|
||
{ id: 1, title: 'Task 1', status: 'pending', dependencies: [] },
|
||
{ id: 2, title: 'Task 2', status: 'pending', dependencies: [1] },
|
||
{
|
||
id: 3,
|
||
title: 'Parent',
|
||
status: 'pending',
|
||
dependencies: [],
|
||
subtasks: [
|
||
{ id: 1, title: 'Sub 3.1', status: 'pending', dependencies: [] }
|
||
]
|
||
}
|
||
]
|
||
},
|
||
other: {
|
||
tasks: [{ id: 99, title: 'Shadow', status: 'pending', dependencies: [1] }]
|
||
}
|
||
});
|
||
|
||
// Utility to deep clone sample each test
|
||
const getFreshData = () => JSON.parse(JSON.stringify(buildSampleTaggedTasks()));
|
||
|
||
// ----- Tests -----
|
||
|
||
describe('removeTask', () => {
|
||
beforeEach(() => {
|
||
jest.clearAllMocks();
|
||
// readJSON returns deep copy so each test isolated
|
||
readJSON.mockImplementation(() => {
|
||
return {
|
||
...getFreshData().master,
|
||
tag: 'master',
|
||
_rawTaggedData: getFreshData()
|
||
};
|
||
});
|
||
writeJSON.mockResolvedValue();
|
||
log.mockImplementation(() => {});
|
||
fs.unlinkSync.mockImplementation(() => {});
|
||
});
|
||
|
||
test('removes a main task and cleans dependencies across tags', async () => {
|
||
const result = await removeTask('tasks/tasks.json', '1', { tag: 'master' });
|
||
|
||
// Expect success true
|
||
expect(result.success).toBe(true);
|
||
// writeJSON called with data where task 1 is gone in master & dependencies removed in other tags
|
||
const written = writeJSON.mock.calls[0][1];
|
||
expect(written.master.tasks.find((t) => t.id === 1)).toBeUndefined();
|
||
// deps removed from child tasks
|
||
const task2 = written.master.tasks.find((t) => t.id === 2);
|
||
expect(task2.dependencies).not.toContain(1);
|
||
const shadow = written.other.tasks.find((t) => t.id === 99);
|
||
expect(shadow.dependencies).not.toContain(1);
|
||
// Task file deletion attempted
|
||
expect(fs.unlinkSync).toHaveBeenCalled();
|
||
});
|
||
|
||
test('removes a subtask only and leaves parent intact', async () => {
|
||
const result = await removeTask('tasks/tasks.json', '3.1', {
|
||
tag: 'master'
|
||
});
|
||
|
||
expect(result.success).toBe(true);
|
||
const written = writeJSON.mock.calls[0][1];
|
||
const parent = written.master.tasks.find((t) => t.id === 3);
|
||
expect(parent.subtasks || []).toHaveLength(0);
|
||
// Ensure parent still exists
|
||
expect(parent).toBeDefined();
|
||
// No task files should be deleted for subtasks
|
||
expect(fs.unlinkSync).not.toHaveBeenCalled();
|
||
});
|
||
|
||
test('handles non-existent task gracefully', async () => {
|
||
const result = await removeTask('tasks/tasks.json', '42', {
|
||
tag: 'master'
|
||
});
|
||
expect(result.success).toBe(false);
|
||
expect(result.error).toContain('not found');
|
||
// writeJSON not called because nothing changed
|
||
expect(writeJSON).not.toHaveBeenCalled();
|
||
});
|
||
});
|