mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-19 10:43:08 +00:00
feat: Add error handling to auto-mode facade and implement followUp feature. Fix Claude weekly usage indicator. Fix mobile card drag
This commit is contained in:
@@ -177,6 +177,66 @@ describe('claude-usage-service.ts', () => {
|
||||
// BEL is stripped, newlines and tabs preserved
|
||||
expect(result).toBe('Line 1\nLine 2\tTabbed with bell');
|
||||
});
|
||||
|
||||
it('should convert cursor forward (ESC[nC) to spaces', () => {
|
||||
const service = new ClaudeUsageService();
|
||||
// Claude CLI TUI uses ESC[1C instead of space between words
|
||||
const input = 'Current\x1B[1Csession';
|
||||
// @ts-expect-error - accessing private method for testing
|
||||
const result = service.stripAnsiCodes(input);
|
||||
|
||||
expect(result).toBe('Current session');
|
||||
});
|
||||
|
||||
it('should handle multi-character cursor forward sequences', () => {
|
||||
const service = new ClaudeUsageService();
|
||||
// ESC[3C = move cursor forward 3 positions = 3 spaces
|
||||
const input = 'Hello\x1B[3Cworld';
|
||||
// @ts-expect-error - accessing private method for testing
|
||||
const result = service.stripAnsiCodes(input);
|
||||
|
||||
expect(result).toBe('Hello world');
|
||||
});
|
||||
|
||||
it('should handle real Claude CLI TUI output with cursor movement codes', () => {
|
||||
const service = new ClaudeUsageService();
|
||||
// Simulates actual Claude CLI /usage output where words are separated by ESC[1C
|
||||
const input =
|
||||
'Current\x1B[1Cweek\x1B[1C(all\x1B[1Cmodels)\n' +
|
||||
'\x1B[32m█████████████████████████▌\x1B[0m\x1B[1C51%\x1B[1Cused\n' +
|
||||
'Resets\x1B[1CFeb\x1B[1C19\x1B[1Cat\x1B[1C3pm\x1B[1C(America/Los_Angeles)';
|
||||
// @ts-expect-error - accessing private method for testing
|
||||
const result = service.stripAnsiCodes(input);
|
||||
|
||||
expect(result).toContain('Current week (all models)');
|
||||
expect(result).toContain('51% used');
|
||||
expect(result).toContain('Resets Feb 19 at 3pm (America/Los_Angeles)');
|
||||
});
|
||||
|
||||
it('should parse usage output with cursor movement codes between words', () => {
|
||||
const service = new ClaudeUsageService();
|
||||
// Simulates the full /usage TUI output with ESC[1C between every word
|
||||
const output =
|
||||
'Current\x1B[1Csession\n' +
|
||||
'\x1B[32m█████████████▌\x1B[0m\x1B[1C27%\x1B[1Cused\n' +
|
||||
'Resets\x1B[1C9pm\x1B[1C(America/Los_Angeles)\n' +
|
||||
'\n' +
|
||||
'Current\x1B[1Cweek\x1B[1C(all\x1B[1Cmodels)\n' +
|
||||
'\x1B[32m█████████████████████████▌\x1B[0m\x1B[1C51%\x1B[1Cused\n' +
|
||||
'Resets\x1B[1CFeb\x1B[1C19\x1B[1Cat\x1B[1C3pm\x1B[1C(America/Los_Angeles)\n' +
|
||||
'\n' +
|
||||
'Current\x1B[1Cweek\x1B[1C(Sonnet\x1B[1Conly)\n' +
|
||||
'\x1B[32m██▌\x1B[0m\x1B[1C5%\x1B[1Cused\n' +
|
||||
'Resets\x1B[1CFeb\x1B[1C19\x1B[1Cat\x1B[1C11pm\x1B[1C(America/Los_Angeles)';
|
||||
// @ts-expect-error - accessing private method for testing
|
||||
const result = service.parseUsageOutput(output);
|
||||
|
||||
expect(result.sessionPercentage).toBe(27);
|
||||
expect(result.weeklyPercentage).toBe(51);
|
||||
expect(result.sonnetWeeklyPercentage).toBe(5);
|
||||
expect(result.weeklyResetText).toContain('Resets Feb 19 at 3pm');
|
||||
expect(result.weeklyResetText).not.toContain('America/Los_Angeles');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseResetTime', () => {
|
||||
|
||||
@@ -151,6 +151,96 @@ describe('FeatureStateManager', () => {
|
||||
expect(savedFeature.justFinishedAt).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should finalize in_progress and pending tasks when moving to waiting_approval', async () => {
|
||||
const featureWithTasks: Feature = {
|
||||
...mockFeature,
|
||||
status: 'in_progress',
|
||||
planSpec: {
|
||||
status: 'approved',
|
||||
version: 1,
|
||||
reviewedByUser: true,
|
||||
currentTaskId: 'task-2',
|
||||
tasksCompleted: 1,
|
||||
tasks: [
|
||||
{ id: 'task-1', title: 'Task 1', status: 'completed', description: 'First task' },
|
||||
{ id: 'task-2', title: 'Task 2', status: 'in_progress', description: 'Second task' },
|
||||
{ id: 'task-3', title: 'Task 3', status: 'pending', description: 'Third task' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
(readJsonWithRecovery as Mock).mockResolvedValue({
|
||||
data: featureWithTasks,
|
||||
recovered: false,
|
||||
source: 'main',
|
||||
});
|
||||
|
||||
await manager.updateFeatureStatus('/project', 'feature-123', 'waiting_approval');
|
||||
|
||||
const savedFeature = (atomicWriteJson as Mock).mock.calls[0][1] as Feature;
|
||||
// All tasks should be completed
|
||||
expect(savedFeature.planSpec?.tasks?.[0].status).toBe('completed');
|
||||
expect(savedFeature.planSpec?.tasks?.[1].status).toBe('completed');
|
||||
expect(savedFeature.planSpec?.tasks?.[2].status).toBe('completed');
|
||||
// currentTaskId should be cleared
|
||||
expect(savedFeature.planSpec?.currentTaskId).toBeUndefined();
|
||||
// tasksCompleted should equal total tasks
|
||||
expect(savedFeature.planSpec?.tasksCompleted).toBe(3);
|
||||
});
|
||||
|
||||
it('should finalize tasks when moving to verified status', async () => {
|
||||
const featureWithTasks: Feature = {
|
||||
...mockFeature,
|
||||
status: 'in_progress',
|
||||
planSpec: {
|
||||
status: 'approved',
|
||||
version: 1,
|
||||
reviewedByUser: true,
|
||||
currentTaskId: 'task-2',
|
||||
tasksCompleted: 1,
|
||||
tasks: [
|
||||
{ id: 'task-1', title: 'Task 1', status: 'completed', description: 'First task' },
|
||||
{ id: 'task-2', title: 'Task 2', status: 'in_progress', description: 'Second task' },
|
||||
{ id: 'task-3', title: 'Task 3', status: 'pending', description: 'Third task' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
(readJsonWithRecovery as Mock).mockResolvedValue({
|
||||
data: featureWithTasks,
|
||||
recovered: false,
|
||||
source: 'main',
|
||||
});
|
||||
|
||||
await manager.updateFeatureStatus('/project', 'feature-123', 'verified');
|
||||
|
||||
const savedFeature = (atomicWriteJson as Mock).mock.calls[0][1] as Feature;
|
||||
// All tasks should be completed
|
||||
expect(savedFeature.planSpec?.tasks?.[0].status).toBe('completed');
|
||||
expect(savedFeature.planSpec?.tasks?.[1].status).toBe('completed');
|
||||
expect(savedFeature.planSpec?.tasks?.[2].status).toBe('completed');
|
||||
// currentTaskId should be cleared
|
||||
expect(savedFeature.planSpec?.currentTaskId).toBeUndefined();
|
||||
// tasksCompleted should equal total tasks
|
||||
expect(savedFeature.planSpec?.tasksCompleted).toBe(3);
|
||||
// justFinishedAt should be cleared for verified
|
||||
expect(savedFeature.justFinishedAt).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle waiting_approval without planSpec tasks gracefully', async () => {
|
||||
(readJsonWithRecovery as Mock).mockResolvedValue({
|
||||
data: { ...mockFeature },
|
||||
recovered: false,
|
||||
source: 'main',
|
||||
});
|
||||
|
||||
await manager.updateFeatureStatus('/project', 'feature-123', 'waiting_approval');
|
||||
|
||||
const savedFeature = (atomicWriteJson as Mock).mock.calls[0][1] as Feature;
|
||||
expect(savedFeature.status).toBe('waiting_approval');
|
||||
expect(savedFeature.justFinishedAt).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create notification for waiting_approval status', async () => {
|
||||
const mockNotificationService = { createNotification: vi.fn() };
|
||||
(getNotificationService as Mock).mockReturnValue(mockNotificationService);
|
||||
|
||||
Reference in New Issue
Block a user