feat: add --compact flag for minimal task list output (#1054)

* feat: add --compact flag for minimal task list output

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Ladi
2025-08-11 18:35:23 +02:00
committed by GitHub
parent 30ca144231
commit 782728ff95
7 changed files with 343 additions and 13 deletions

View File

@@ -22,7 +22,10 @@ jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
),
addComplexityToTask: jest.fn(),
readComplexityReport: jest.fn(() => null),
getTagAwareFilePath: jest.fn((tag, path) => '/mock/tagged/report.json')
getTagAwareFilePath: jest.fn((tag, path) => '/mock/tagged/report.json'),
stripAnsiCodes: jest.fn((text) =>
text ? text.replace(/\x1b\[[0-9;]*m/g, '') : text
)
}));
jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
@@ -45,8 +48,13 @@ jest.unstable_mockModule(
);
// Import the mocked modules
const { readJSON, log, readComplexityReport, addComplexityToTask } =
await import('../../../../../scripts/modules/utils.js');
const {
readJSON,
log,
readComplexityReport,
addComplexityToTask,
stripAnsiCodes
} = await import('../../../../../scripts/modules/utils.js');
const { displayTaskList } = await import(
'../../../../../scripts/modules/ui.js'
);
@@ -584,4 +592,140 @@ describe('listTasks', () => {
expect(taskIds).toContain(5); // review task
});
});
describe('Compact output format', () => {
test('should output compact format when outputFormat is compact', async () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const tasksPath = 'tasks/tasks.json';
await listTasks(tasksPath, null, null, false, 'compact', {
tag: 'master'
});
expect(consoleSpy).toHaveBeenCalled();
const output = consoleSpy.mock.calls.map((call) => call[0]).join('\n');
// Strip ANSI color codes for testing
const cleanOutput = stripAnsiCodes(output);
// Should contain compact format elements: ID status title (priority) [→ dependencies]
expect(cleanOutput).toContain('1 done Setup Project (high)');
expect(cleanOutput).toContain(
'2 pending Implement Core Features (high) → 1'
);
consoleSpy.mockRestore();
});
test('should format single task compactly', async () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const tasksPath = 'tasks/tasks.json';
await listTasks(tasksPath, null, null, false, 'compact', {
tag: 'master'
});
expect(consoleSpy).toHaveBeenCalled();
const output = consoleSpy.mock.calls.map((call) => call[0]).join('\n');
// Should be compact (no verbose headers)
expect(output).not.toContain('Project Dashboard');
expect(output).not.toContain('Progress:');
consoleSpy.mockRestore();
});
test('should handle compact format with subtasks', async () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const tasksPath = 'tasks/tasks.json';
await listTasks(
tasksPath,
null,
null,
true, // withSubtasks = true
'compact',
{ tag: 'master' }
);
expect(consoleSpy).toHaveBeenCalled();
const output = consoleSpy.mock.calls.map((call) => call[0]).join('\n');
// Strip ANSI color codes for testing
const cleanOutput = stripAnsiCodes(output);
// Should handle both tasks and subtasks
expect(cleanOutput).toContain('1 done Setup Project (high)');
expect(cleanOutput).toContain('3.1 done Create Header Component');
consoleSpy.mockRestore();
});
test('should handle empty task list in compact format', async () => {
readJSON.mockReturnValue({ tasks: [] });
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const tasksPath = 'tasks/tasks.json';
await listTasks(tasksPath, null, null, false, 'compact', {
tag: 'master'
});
expect(consoleSpy).toHaveBeenCalledWith('No tasks found');
consoleSpy.mockRestore();
});
test('should format dependencies correctly with shared helper', async () => {
// Create mock tasks with various dependency scenarios
const tasksWithDeps = {
tasks: [
{
id: 1,
title: 'Task with no dependencies',
status: 'pending',
priority: 'medium',
dependencies: []
},
{
id: 2,
title: 'Task with few dependencies',
status: 'pending',
priority: 'high',
dependencies: [1, 3]
},
{
id: 3,
title: 'Task with many dependencies',
status: 'pending',
priority: 'low',
dependencies: [1, 2, 4, 5, 6, 7, 8, 9]
}
]
};
readJSON.mockReturnValue(tasksWithDeps);
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const tasksPath = 'tasks/tasks.json';
await listTasks(tasksPath, null, null, false, 'compact', {
tag: 'master'
});
expect(consoleSpy).toHaveBeenCalled();
const output = consoleSpy.mock.calls.map((call) => call[0]).join('\n');
// Strip ANSI color codes for testing
const cleanOutput = stripAnsiCodes(output);
// Should format tasks correctly with compact output including priority
expect(cleanOutput).toContain(
'1 pending Task with no dependencies (medium)'
);
expect(cleanOutput).toContain('Task with few dependencies');
expect(cleanOutput).toContain('Task with many dependencies');
// Should show dependencies with arrow when they exist
expect(cleanOutput).toMatch(/2.*→.*1,3/);
// Should truncate many dependencies with "+X more" format
expect(cleanOutput).toMatch(/3.*→.*1,2,4,5,6.*\(\+\d+ more\)/);
consoleSpy.mockRestore();
});
});
});