Files
claude-task-master/tests/unit/scripts/modules/task-manager/generate-task-files.test.js
Eyal Toledano d5360f625f fix: resolve all remaining test failures and improve test reliability
- Fix clear-subtasks test by implementing deep copy of mock data to prevent mutation issues between tests
- Fix add-task test by uncommenting and properly configuring generateTaskFiles call with correct parameters
- Fix analyze-task-complexity tests by properly mocking fs.writeFileSync with shared mock function
- Update test expectations to match actual function signatures and data structures
- Improve mock setup consistency across all test suites
- Ensure all tests now pass (329 total: 318 passed, 11 skipped, 0 failed)
2025-06-13 22:31:24 -04:00

303 lines
7.9 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}`),
ensureTagMetadata: jest.fn()
}));
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, ensureTagMetadata } =
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 - updated to tagged format
const sampleTasksData = {
master: {
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'
}
]
}
],
metadata: {
projectName: 'Test Project',
created: '2024-01-01T00:00:00.000Z',
updated: '2024-01-01T00:00:00.000Z'
}
}
};
beforeEach(() => {
jest.clearAllMocks();
// Mock readJSON to return the full tagged structure
readJSON.mockImplementation((tasksPath, projectRoot, tag) => {
if (tag && sampleTasksData[tag]) {
return {
...sampleTasksData[tag],
tag,
_rawTaggedData: sampleTasksData
};
}
// Default to master if no tag or tag not found
return {
...sampleTasksData.master,
tag: 'master',
_rawTaggedData: sampleTasksData
};
});
});
test('should generate task files from tasks.json - working test', async () => {
// Set up mocks for this specific test
fs.existsSync.mockReturnValue(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 with new signature, defaulting to master
expect(readJSON).toHaveBeenCalledWith(tasksPath, undefined);
// Verify dependencies were validated with the raw tagged data
expect(validateAndFixDependencies).toHaveBeenCalledWith(
sampleTasksData,
tasksPath,
undefined,
'master'
);
// Verify files were written for each task in the master tag
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
fs.existsSync.mockReturnValue(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
// It will be called multiple times, once for each task that has dependencies.
expect(formatDependenciesWithStatus).toHaveBeenCalled();
});
test('should handle tasks with no subtasks', async () => {
// Create data with tasks that have no subtasks - updated to tagged format
const tasksWithoutSubtasks = {
master: {
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'
}
],
metadata: {
projectName: 'Test Project',
created: '2024-01-01T00:00:00.000Z',
updated: '2024-01-01T00:00:00.000Z'
}
}
};
// Update the mock for this specific test case
readJSON.mockImplementation((tasksPath, projectRoot, tag) => {
return {
...tasksWithoutSubtasks.master,
tag: 'master',
_rawTaggedData: tasksWithoutSubtasks
};
});
fs.existsSync.mockReturnValue(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 validate dependencies before generating files', async () => {
// Set up mocks
fs.existsSync.mockReturnValue(true);
// Call the function
await generateTaskFiles('tasks/tasks.json', 'tasks', {
mcpLog: { info: jest.fn() }
});
// Verify validateAndFixDependencies was called with the raw tagged data
expect(validateAndFixDependencies).toHaveBeenCalledWith(
sampleTasksData,
'tasks/tasks.json',
undefined,
'master'
);
});
});