/**
* Unit test to ensure fixDependenciesCommand writes JSON with the correct
* projectRoot and tag arguments so that tag data is preserved.
*/
import { jest } from '@jest/globals';
// Mock process.exit to prevent test termination
const mockProcessExit = jest.fn();
const originalExit = process.exit;
process.exit = mockProcessExit;
// Mock utils.js BEFORE importing the module under test
jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
readJSON: jest.fn(),
writeJSON: jest.fn(),
log: jest.fn(),
findProjectRoot: jest.fn(() => '/mock/project/root'),
getCurrentTag: jest.fn(() => 'master'),
taskExists: jest.fn(() => true),
formatTaskId: jest.fn((id) => id),
findCycles: jest.fn(() => []),
traverseDependencies: jest.fn((sourceTasks, allTasks, options = {}) => []),
isSilentMode: jest.fn(() => true),
resolveTag: jest.fn(() => 'master'),
getTasksForTag: jest.fn(() => []),
setTasksForTag: jest.fn(),
enableSilentMode: jest.fn(),
disableSilentMode: jest.fn(),
isEmpty: jest.fn((value) => {
if (value === null || value === undefined) return true;
if (Array.isArray(value)) return value.length === 0;
if (typeof value === 'object' && value !== null)
return Object.keys(value).length === 0;
return false; // Not an array or object
}),
resolveEnvVariable: jest.fn()
}));
// Mock ui.js
jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
displayBanner: jest.fn(),
formatDependenciesWithStatus: jest.fn()
}));
// Mock task-manager.js
// Mock external libraries
jest.unstable_mockModule('chalk', () => ({
default: {
green: jest.fn((text) => text),
cyan: jest.fn((text) => text),
bold: jest.fn((text) => text)
}
}));
jest.unstable_mockModule('boxen', () => ({
default: jest.fn((text) => text)
}));
// Import the mocked modules
const { readJSON, writeJSON, log, taskExists } = await import(
'../../../../../scripts/modules/utils.js'
);
// Import the module under test
const { fixDependenciesCommand } = await import(
'../../../../../scripts/modules/dependency-manager.js'
);
describe('fixDependenciesCommand tag preservation', () => {
beforeEach(() => {
jest.clearAllMocks();
mockProcessExit.mockClear();
});
afterAll(() => {
// Restore original process.exit
process.exit = originalExit;
});
it('calls writeJSON with projectRoot and tag parameters when changes are made', async () => {
const tasksPath = '/mock/tasks.json';
const projectRoot = '/mock/project/root';
const tag = 'master';
// Mock data WITH dependency issues to trigger writeJSON
const tasksDataWithIssues = {
tasks: [
{
id: 1,
title: 'Task 1',
dependencies: [999] // Non-existent dependency to trigger fix
},
{
id: 2,
title: 'Task 2',
dependencies: []
}
],
tag: 'master',
_rawTaggedData: {
master: {
tasks: [
{
id: 1,
title: 'Task 1',
dependencies: [999]
}
]
}
}
};
readJSON.mockReturnValue(tasksDataWithIssues);
taskExists.mockReturnValue(false); // Make dependency invalid to trigger fix
await fixDependenciesCommand(tasksPath, {
context: { projectRoot, tag }
});
// Verify readJSON was called with correct parameters
expect(readJSON).toHaveBeenCalledWith(tasksPath, projectRoot, tag);
// Verify writeJSON was called (should be triggered by removing invalid dependency)
expect(writeJSON).toHaveBeenCalled();
// Check the writeJSON call parameters
const writeJSONCalls = writeJSON.mock.calls;
const lastWriteCall = writeJSONCalls[writeJSONCalls.length - 1];
const [calledPath, _data, calledProjectRoot, calledTag] = lastWriteCall;
expect(calledPath).toBe(tasksPath);
expect(calledProjectRoot).toBe(projectRoot);
expect(calledTag).toBe(tag);
// Verify process.exit was NOT called (meaning the function succeeded)
expect(mockProcessExit).not.toHaveBeenCalled();
});
it('does not call writeJSON when no changes are needed', async () => {
const tasksPath = '/mock/tasks.json';
const projectRoot = '/mock/project/root';
const tag = 'master';
// Mock data WITHOUT dependency issues (no changes needed)
const cleanTasksData = {
tasks: [
{
id: 1,
title: 'Task 1',
dependencies: [] // Clean, no issues
}
],
tag: 'master'
};
readJSON.mockReturnValue(cleanTasksData);
taskExists.mockReturnValue(true); // All dependencies exist
await fixDependenciesCommand(tasksPath, {
context: { projectRoot, tag }
});
// Verify readJSON was called
expect(readJSON).toHaveBeenCalledWith(tasksPath, projectRoot, tag);
// Verify writeJSON was NOT called (no changes needed)
expect(writeJSON).not.toHaveBeenCalled();
// Verify process.exit was NOT called
expect(mockProcessExit).not.toHaveBeenCalled();
});
it('handles early exit when no valid tasks found', async () => {
const tasksPath = '/mock/tasks.json';
// Mock invalid data to trigger early exit
readJSON.mockReturnValue(null);
await fixDependenciesCommand(tasksPath, {
context: { projectRoot: '/mock', tag: 'master' }
});
// Verify readJSON was called
expect(readJSON).toHaveBeenCalled();
// Verify writeJSON was NOT called (early exit)
expect(writeJSON).not.toHaveBeenCalled();
// Verify process.exit WAS called due to invalid data
expect(mockProcessExit).toHaveBeenCalledWith(1);
});
});