mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2025-11-13 08:35:08 +00:00
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
203 lines
5.3 KiB
TypeScript
203 lines
5.3 KiB
TypeScript
/**
|
|
* @fileoverview Unit tests for autopilot shared utilities
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import {
|
|
validateTaskId,
|
|
parseSubtasks,
|
|
OutputFormatter
|
|
} from '../../../../src/commands/autopilot/shared.js';
|
|
|
|
// Mock fs-extra
|
|
vi.mock('fs-extra', () => ({
|
|
default: {
|
|
pathExists: vi.fn(),
|
|
readJSON: vi.fn(),
|
|
writeJSON: vi.fn(),
|
|
ensureDir: vi.fn(),
|
|
remove: vi.fn()
|
|
},
|
|
pathExists: vi.fn(),
|
|
readJSON: vi.fn(),
|
|
writeJSON: vi.fn(),
|
|
ensureDir: vi.fn(),
|
|
remove: vi.fn()
|
|
}));
|
|
|
|
describe('Autopilot Shared Utilities', () => {
|
|
const projectRoot = '/test/project';
|
|
const statePath = `${projectRoot}/.taskmaster/workflow-state.json`;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('validateTaskId', () => {
|
|
it('should validate simple task IDs', () => {
|
|
expect(validateTaskId('1')).toBe(true);
|
|
expect(validateTaskId('10')).toBe(true);
|
|
expect(validateTaskId('999')).toBe(true);
|
|
});
|
|
|
|
it('should validate subtask IDs', () => {
|
|
expect(validateTaskId('1.1')).toBe(true);
|
|
expect(validateTaskId('1.2')).toBe(true);
|
|
expect(validateTaskId('10.5')).toBe(true);
|
|
});
|
|
|
|
it('should validate nested subtask IDs', () => {
|
|
expect(validateTaskId('1.1.1')).toBe(true);
|
|
expect(validateTaskId('1.2.3')).toBe(true);
|
|
});
|
|
|
|
it('should reject invalid formats', () => {
|
|
expect(validateTaskId('')).toBe(false);
|
|
expect(validateTaskId('abc')).toBe(false);
|
|
expect(validateTaskId('1.')).toBe(false);
|
|
expect(validateTaskId('.1')).toBe(false);
|
|
expect(validateTaskId('1..2')).toBe(false);
|
|
expect(validateTaskId('1.2.3.')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('parseSubtasks', () => {
|
|
it('should parse subtasks from task data', () => {
|
|
const task = {
|
|
id: '1',
|
|
title: 'Test Task',
|
|
subtasks: [
|
|
{ id: '1', title: 'Subtask 1', status: 'pending' },
|
|
{ id: '2', title: 'Subtask 2', status: 'done' },
|
|
{ id: '3', title: 'Subtask 3', status: 'in-progress' }
|
|
]
|
|
};
|
|
|
|
const result = parseSubtasks(task, 5);
|
|
|
|
expect(result).toHaveLength(3);
|
|
expect(result[0]).toEqual({
|
|
id: '1',
|
|
title: 'Subtask 1',
|
|
status: 'pending',
|
|
attempts: 0,
|
|
maxAttempts: 5
|
|
});
|
|
expect(result[1]).toEqual({
|
|
id: '2',
|
|
title: 'Subtask 2',
|
|
status: 'completed',
|
|
attempts: 0,
|
|
maxAttempts: 5
|
|
});
|
|
});
|
|
|
|
it('should return empty array for missing subtasks', () => {
|
|
const task = { id: '1', title: 'Test Task' };
|
|
expect(parseSubtasks(task)).toEqual([]);
|
|
});
|
|
|
|
it('should use default maxAttempts', () => {
|
|
const task = {
|
|
subtasks: [{ id: '1', title: 'Subtask 1', status: 'pending' }]
|
|
};
|
|
|
|
const result = parseSubtasks(task);
|
|
expect(result[0].maxAttempts).toBe(3);
|
|
});
|
|
});
|
|
|
|
// State persistence tests omitted - covered in integration tests
|
|
|
|
describe('OutputFormatter', () => {
|
|
let consoleLogSpy: any;
|
|
let consoleErrorSpy: any;
|
|
|
|
beforeEach(() => {
|
|
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
});
|
|
|
|
afterEach(() => {
|
|
consoleLogSpy.mockRestore();
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
describe('JSON mode', () => {
|
|
it('should output JSON for success', () => {
|
|
const formatter = new OutputFormatter(true);
|
|
formatter.success('Test message', { key: 'value' });
|
|
|
|
expect(consoleLogSpy).toHaveBeenCalled();
|
|
const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
|
|
expect(output.success).toBe(true);
|
|
expect(output.message).toBe('Test message');
|
|
expect(output.key).toBe('value');
|
|
});
|
|
|
|
it('should output JSON for error', () => {
|
|
const formatter = new OutputFormatter(true);
|
|
formatter.error('Error message', { code: 'ERR001' });
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
const output = JSON.parse(consoleErrorSpy.mock.calls[0][0]);
|
|
expect(output.error).toBe('Error message');
|
|
expect(output.code).toBe('ERR001');
|
|
});
|
|
|
|
it('should output JSON for data', () => {
|
|
const formatter = new OutputFormatter(true);
|
|
formatter.output({ test: 'data' });
|
|
|
|
expect(consoleLogSpy).toHaveBeenCalled();
|
|
const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
|
|
expect(output.test).toBe('data');
|
|
});
|
|
});
|
|
|
|
describe('Text mode', () => {
|
|
it('should output formatted text for success', () => {
|
|
const formatter = new OutputFormatter(false);
|
|
formatter.success('Test message');
|
|
|
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
expect.stringContaining('✓ Test message')
|
|
);
|
|
});
|
|
|
|
it('should output formatted text for error', () => {
|
|
const formatter = new OutputFormatter(false);
|
|
formatter.error('Error message');
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
expect.stringContaining('Error: Error message')
|
|
);
|
|
});
|
|
|
|
it('should output formatted text for warning', () => {
|
|
const consoleWarnSpy = vi
|
|
.spyOn(console, 'warn')
|
|
.mockImplementation(() => {});
|
|
const formatter = new OutputFormatter(false);
|
|
formatter.warning('Warning message');
|
|
|
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
expect.stringContaining('⚠ Warning message')
|
|
);
|
|
consoleWarnSpy.mockRestore();
|
|
});
|
|
|
|
it('should not output info in JSON mode', () => {
|
|
const formatter = new OutputFormatter(true);
|
|
formatter.info('Info message');
|
|
|
|
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|
|
});
|