claude-task-master/tests/unit/scripts/modules/task-manager/update-subtask-by-id.test.js
Ralph Khreish 37af0f1912
feat: add codebase context capabilities to gemini-cli (#1163)
* feat: add support for claude code context

- code context for:
  - add-task
  - update-subtask
  - update-task
  - update

* feat: fix CI and format + refactor

* chore: format

* chore: fix test

* feat: add gemini-cli support for codebase context

* feat: add google cli integration and fix tests

* chore: apply requested coderabbit changes

* chore: bump gemini cli package
2025-08-28 22:44:52 +02:00

203 lines
4.7 KiB
JavaScript

import { jest } from '@jest/globals';
// Provide fs mock early so existsSync can be stubbed
jest.unstable_mockModule('fs', () => {
const mockFs = {
existsSync: jest.fn(() => true),
writeFileSync: jest.fn(),
readFileSync: jest.fn(),
unlinkSync: jest.fn()
};
return { default: mockFs, ...mockFs };
});
// --- Mock dependencies ---
jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
readJSON: jest.fn(),
writeJSON: jest.fn(),
log: jest.fn(),
isSilentMode: jest.fn(() => false),
findProjectRoot: jest.fn(() => '/project'),
flattenTasksWithSubtasks: jest.fn(() => []),
truncate: jest.fn((t) => t),
isEmpty: jest.fn(() => false),
resolveEnvVariable: jest.fn(),
findTaskById: jest.fn(),
getCurrentTag: jest.fn(() => 'master')
}));
jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
getStatusWithColor: jest.fn((s) => s),
startLoadingIndicator: jest.fn(() => ({ stop: jest.fn() })),
stopLoadingIndicator: jest.fn(),
displayAiUsageSummary: jest.fn()
}));
jest.unstable_mockModule(
'../../../../../scripts/modules/task-manager/generate-task-files.js',
() => ({
default: jest.fn().mockResolvedValue()
})
);
jest.unstable_mockModule(
'../../../../../scripts/modules/ai-services-unified.js',
() => ({
generateTextService: jest
.fn()
.mockResolvedValue({ mainResult: { content: '' }, telemetryData: {} })
})
);
jest.unstable_mockModule(
'../../../../../scripts/modules/config-manager.js',
() => ({
getDebugFlag: jest.fn(() => false),
hasCodebaseAnalysis: jest.fn(() => false)
})
);
jest.unstable_mockModule(
'../../../../../scripts/modules/prompt-manager.js',
() => ({
default: jest.fn().mockReturnValue({
loadPrompt: jest.fn().mockReturnValue('Update the subtask')
}),
getPromptManager: jest.fn().mockReturnValue({
loadPrompt: jest.fn().mockReturnValue('Update the subtask')
})
})
);
jest.unstable_mockModule(
'../../../../../scripts/modules/utils/contextGatherer.js',
() => ({
ContextGatherer: jest.fn().mockImplementation(() => ({
gather: jest.fn().mockReturnValue({
fullContext: '',
summary: ''
})
}))
})
);
// Import mocked utils to leverage mocks later
const { readJSON, log } = await import(
'../../../../../scripts/modules/utils.js'
);
// Import function under test
const { default: updateSubtaskById } = await import(
'../../../../../scripts/modules/task-manager/update-subtask-by-id.js'
);
describe('updateSubtaskById validation', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
});
test('throws error on invalid subtask id format', async () => {
await expect(
updateSubtaskById(
'tasks/tasks.json',
'invalid',
'my prompt',
false,
{
tag: 'master'
},
'json'
)
).rejects.toThrow('Invalid subtask ID format');
});
test('throws error when prompt is empty', async () => {
await expect(
updateSubtaskById(
'tasks/tasks.json',
'1.1',
'',
false,
{ tag: 'master' },
'json'
)
).rejects.toThrow('Prompt cannot be empty');
});
test('throws error if tasks file does not exist', async () => {
// Mock fs.existsSync to return false via jest.spyOn (dynamic import of fs)
const fs = await import('fs');
fs.existsSync.mockReturnValue(false);
await expect(
updateSubtaskById(
'tasks/tasks.json',
'1.1',
'prompt',
false,
{
tag: 'master'
},
'json'
)
).rejects.toThrow('Tasks file not found');
});
test('throws error if parent task missing', async () => {
// Mock existsSync true
const fs = await import('fs');
fs.existsSync.mockReturnValue(true);
// readJSON returns tasks without parent id 1
readJSON.mockReturnValue({ tag: 'master', tasks: [] });
await expect(
updateSubtaskById(
'tasks/tasks.json',
'1.1',
'prompt',
false,
{
tag: 'master'
},
'json'
)
).rejects.toThrow('Parent task with ID 1 not found');
// log called with error level
expect(log).toHaveBeenCalled();
});
test('successfully updates subtask with valid inputs', async () => {
const fs = await import('fs');
const { writeJSON } = await import(
'../../../../../scripts/modules/utils.js'
);
fs.existsSync.mockReturnValue(true);
readJSON.mockReturnValue({
tag: 'master',
tasks: [
{
id: 1,
title: 'Parent Task',
subtasks: [{ id: 1, title: 'Original subtask', status: 'pending' }]
}
]
});
// updateSubtaskById doesn't return a value on success, it just executes
await expect(
updateSubtaskById(
'tasks/tasks.json',
'1.1',
'Update this subtask',
false,
{ tag: 'master' },
'json'
)
).resolves.not.toThrow();
expect(writeJSON).toHaveBeenCalled();
});
});