diff --git a/php/Console/Commands/TaskCommand.php b/php/Console/Commands/TaskCommand.php index fd2f127..065f77a 100644 --- a/php/Console/Commands/TaskCommand.php +++ b/php/Console/Commands/TaskCommand.php @@ -1,5 +1,7 @@ $this->listTasks(), 'add', 'new' => $this->addTask(), + 'update', 'edit' => $this->updateTask(), + 'toggle', 'flip' => $this->toggleTask(), 'done', 'complete' => $this->completeTask(), 'start', 'wip' => $this->startTask(), 'remove', 'rm', 'delete' => $this->removeTask(), @@ -161,6 +165,98 @@ class TaskCommand extends Command return 0; } + protected function updateTask(): int + { + $task = $this->findTask('update'); + + if (! $task) { + return 1; + } + + $updates = []; + + $title = $this->option('title'); + if ($title !== null) { + $title = trim((string) $title); + if ($title === '') { + $this->error('Title cannot be empty'); + + return 1; + } + + $updates['title'] = $title; + } + + $description = $this->option('desc'); + if ($description !== null) { + $updates['description'] = (string) $description; + } + + $priority = $this->option('priority'); + if ($this->input->hasParameterOption('--priority')) { + $allowed = ['low', 'normal', 'high', 'urgent']; + if (! in_array($priority, $allowed, true)) { + $this->error('Priority must be one of: low, normal, high, urgent'); + + return 1; + } + + $updates['priority'] = $priority; + } + + $category = $this->option('category'); + if ($category !== null) { + $updates['category'] = (string) $category; + } + + $file = $this->option('file'); + if ($file !== null) { + $updates['file_ref'] = (string) $file; + } + + $line = $this->option('line'); + if ($line !== null) { + if (! is_numeric($line)) { + $this->error('Line must be a number'); + + return 1; + } + + $updates['line_ref'] = (int) $line; + } + + if ($updates === []) { + $this->error('Provide at least one field to update: --title, --desc, --priority, --category, --file, or --line'); + + return 1; + } + + $task->update($updates); + + $fresh = $task->fresh(); + $this->info("Updated: {$fresh->title}"); + + return 0; + } + + protected function toggleTask(): int + { + $task = $this->findTask('toggle'); + + if (! $task) { + return 1; + } + + $currentStatus = $task->status; + $newStatus = $currentStatus === 'done' ? 'pending' : 'done'; + $task->update(['status' => $newStatus]); + + $fresh = $task->fresh(); + $this->info("Toggled: {$fresh->title} {$currentStatus} → {$fresh->status}"); + + return 0; + } + protected function completeTask(): int { $task = $this->findTask('complete'); @@ -278,6 +374,8 @@ class TaskCommand extends Command $this->line(' Usage:'); $this->line(' php artisan task list List active tasks'); $this->line(' php artisan task add --title="Fix bug" Add a task'); + $this->line(' php artisan task update --id=1 --desc="..." Update task details'); + $this->line(' php artisan task toggle --id=1 Toggle task completion'); $this->line(' php artisan task start --id=1 Start working on task'); $this->line(' php artisan task done --id=1 Complete a task'); $this->line(' php artisan task show --id=1 Show task details'); diff --git a/php/tests/Feature/TaskCommandTest.php b/php/tests/Feature/TaskCommandTest.php new file mode 100644 index 0000000..96aba99 --- /dev/null +++ b/php/tests/Feature/TaskCommandTest.php @@ -0,0 +1,114 @@ +workspace = Workspace::factory()->create(); + } + + public function test_it_updates_task_details(): void + { + $task = Task::create([ + 'workspace_id' => $this->workspace->id, + 'title' => 'Original title', + 'description' => 'Original description', + 'priority' => 'normal', + 'category' => 'task', + 'file_ref' => 'app/Original.php', + 'line_ref' => 12, + 'status' => 'pending', + ]); + + $this->artisan('task', [ + 'action' => 'update', + '--workspace' => $this->workspace->id, + '--id' => $task->id, + '--title' => 'Updated title', + '--desc' => 'Updated description', + '--priority' => 'urgent', + '--category' => 'docs', + '--file' => 'app/Updated.php', + '--line' => 88, + ]) + ->expectsOutputToContain('Updated: Updated title') + ->assertSuccessful(); + + $this->assertDatabaseHas('tasks', [ + 'id' => $task->id, + 'workspace_id' => $this->workspace->id, + 'title' => 'Updated title', + 'description' => 'Updated description', + 'priority' => 'urgent', + 'category' => 'docs', + 'file_ref' => 'app/Updated.php', + 'line_ref' => 88, + 'status' => 'pending', + ]); + } + + public function test_it_toggles_task_completion(): void + { + $task = Task::create([ + 'workspace_id' => $this->workspace->id, + 'title' => 'Toggle me', + 'status' => 'pending', + ]); + + $this->artisan('task', [ + 'action' => 'toggle', + '--workspace' => $this->workspace->id, + '--id' => $task->id, + ]) + ->expectsOutputToContain('Toggled: Toggle me pending → done') + ->assertSuccessful(); + + $this->assertSame('done', $task->fresh()->status); + + $this->artisan('task', [ + 'action' => 'toggle', + '--workspace' => $this->workspace->id, + '--id' => $task->id, + ]) + ->expectsOutputToContain('Toggled: Toggle me done → pending') + ->assertSuccessful(); + + $this->assertSame('pending', $task->fresh()->status); + } + + public function test_it_rejects_update_without_any_changes(): void + { + $task = Task::create([ + 'workspace_id' => $this->workspace->id, + 'title' => 'No change', + 'status' => 'pending', + ]); + + $this->artisan('task', [ + 'action' => 'update', + '--workspace' => $this->workspace->id, + '--id' => $task->id, + ]) + ->expectsOutputToContain('Provide at least one field to update') + ->assertFailed(); + + $this->assertSame('No change', $task->fresh()->title); + $this->assertSame('pending', $task->fresh()->status); + } +}