Files
claude-task-master/tests/unit/scripts/modules/task-manager/remove-task.test.js
2025-11-19 18:03:34 +01:00

123 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
});
});