package integration import ( "testing" "github.com/boolean-maybe/tiki/model" taskpkg "github.com/boolean-maybe/tiki/task" "github.com/boolean-maybe/tiki/testutil" "github.com/gdamore/tcell/v2" ) // TestTaskEdit_ShiftTabBackward verifies Shift+Tab navigates backward through fields func TestTaskEdit_ShiftTabBackward(t *testing.T) { ta := testutil.NewTestApp(t) defer ta.Cleanup() // Create task taskID := "TIKI-1" if err := testutil.CreateTestTask(ta.TaskDir, taskID, "Test Task", taskpkg.StatusReady, taskpkg.TypeStory); err != nil { t.Fatalf("failed to create task: %v", err) } if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } // Navigate: Board → Task Detail → Task Edit ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil) ta.Draw() ta.SendKey(tcell.KeyEnter, 0, tcell.ModNone) ta.SendKey(tcell.KeyRune, 'e', tcell.ModNone) // Tab forward to Assignee (Title → Status → Type → Priority → Points → Assignee) for i := 0; i < 5; i++ { ta.SendKey(tcell.KeyTab, 0, tcell.ModNone) } // Now Shift+Tab backward (Assignee → Points) ta.SendKey(tcell.KeyBacktab, 0, tcell.ModNone) // Change Points field via Up arrow to verify focus is on Points ta.SendKeyToFocused(tcell.KeyUp, 0, tcell.ModNone) // 1 → 2 // Save ta.SendKey(tcell.KeyCtrlS, 0, tcell.ModNone) // Reload and verify points was changed if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } task := ta.TaskStore.GetTask(taskID) if task == nil { t.Fatalf("task not found") return } // Verify points changed from default 1 to 2 if task.Points != 2 { t.Errorf("points = %d, want 2 after Shift+Tab to Points field", task.Points) } } // TestTaskEdit_StatusCycling verifies arrow keys cycle through all status values func TestTaskEdit_StatusCycling(t *testing.T) { ta := testutil.NewTestApp(t) defer ta.Cleanup() // Create task taskID := "TIKI-1" if err := testutil.CreateTestTask(ta.TaskDir, taskID, "Test Task", taskpkg.StatusReady, taskpkg.TypeStory); err != nil { t.Fatalf("failed to create task: %v", err) } if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } // Navigate: Board → Task Detail → Task Edit ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil) ta.Draw() ta.SendKey(tcell.KeyEnter, 0, tcell.ModNone) ta.SendKey(tcell.KeyRune, 'e', tcell.ModNone) // Tab to Status field ta.SendKey(tcell.KeyTab, 0, tcell.ModNone) // Current status is "ready" // Test status cycling by pressing Down multiple times without saving between presses // Status order: Backlog -> Ready -> In Progress -> Review -> Done // Note: The dropdown doesn't wrap, so we can only go forward through the list // Press Down 3 times: Ready → In Progress → Review → Done ta.SendKeyToFocused(tcell.KeyDown, 0, tcell.ModNone) // Ready → In Progress ta.SendKeyToFocused(tcell.KeyDown, 0, tcell.ModNone) // In Progress → Review ta.SendKeyToFocused(tcell.KeyDown, 0, tcell.ModNone) // Review → Done // Save ta.SendKey(tcell.KeyCtrlS, 0, tcell.ModNone) // Reload and verify final status if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } task := ta.TaskStore.GetTask(taskID) if task == nil { t.Fatalf("task not found") return } if task.Status != taskpkg.StatusDone { t.Errorf("after 3 Down presses, status = %v, want done", task.Status) } } // TestTaskEdit_TypeToggling verifies cycling through all type values (Story → Bug → Spike → Epic) func TestTaskEdit_TypeToggling(t *testing.T) { ta := testutil.NewTestApp(t) defer ta.Cleanup() // Create task with Story type taskID := "TIKI-1" if err := testutil.CreateTestTask(ta.TaskDir, taskID, "Test Task", taskpkg.StatusReady, taskpkg.TypeStory); err != nil { t.Fatalf("failed to create task: %v", err) } if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } // Navigate: Board → Task Detail → Task Edit ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil) ta.Draw() ta.SendKey(tcell.KeyEnter, 0, tcell.ModNone) ta.SendKey(tcell.KeyRune, 'e', tcell.ModNone) // Tab to Type field (Title → Status → Type) ta.SendKey(tcell.KeyTab, 0, tcell.ModNone) ta.SendKey(tcell.KeyTab, 0, tcell.ModNone) // Test type cycling by pressing Down multiple times without saving between presses // Type order: Story → Bug → Spike → Epic → (wraps to) Story // Press Down 3 times: Story → Bug → Spike → Epic ta.SendKeyToFocused(tcell.KeyDown, 0, tcell.ModNone) // Story → Bug ta.SendKeyToFocused(tcell.KeyDown, 0, tcell.ModNone) // Bug → Spike ta.SendKeyToFocused(tcell.KeyDown, 0, tcell.ModNone) // Spike → Epic // Save ta.SendKey(tcell.KeyCtrlS, 0, tcell.ModNone) // Reload and verify final type if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } task := ta.TaskStore.GetTask(taskID) if task == nil { t.Fatalf("task not found") return } if task.Type != taskpkg.TypeEpic { t.Errorf("after 3 Down presses, type = %v, want epic", task.Type) } } // TestTaskEdit_AssigneeInput verifies typing in assignee field // Note: Current behavior appends to default "Unassigned" text rather than replacing it. // This is known behavior and left as-is for now. func TestTaskEdit_AssigneeInput(t *testing.T) { ta := testutil.NewTestApp(t) defer ta.Cleanup() // Create task taskID := "TIKI-1" if err := testutil.CreateTestTask(ta.TaskDir, taskID, "Test Task", taskpkg.StatusReady, taskpkg.TypeStory); err != nil { t.Fatalf("failed to create task: %v", err) } if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } // Navigate: Board → Task Detail → Task Edit ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil) ta.Draw() ta.SendKey(tcell.KeyEnter, 0, tcell.ModNone) ta.SendKey(tcell.KeyRune, 'e', tcell.ModNone) // Tab to Assignee field (Title → Status → Type → Priority → Points → Assignee) for i := 0; i < 5; i++ { ta.SendKey(tcell.KeyTab, 0, tcell.ModNone) } // Type assignee name // Current behavior: text appends to "Unassigned" default ta.SendText("john.doe") // Save ta.SendKey(tcell.KeyCtrlS, 0, tcell.ModNone) // Reload and verify if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } task := ta.TaskStore.GetTask(taskID) if task == nil { t.Fatalf("task not found") return } // Current behavior: appends to default "Unassigned" text expected := "Unassignedjohn.doe" if task.Assignee != expected { t.Errorf("assignee = %q, want %q", task.Assignee, expected) } } // TestTaskEdit_SaveAndContinue verifies saving within an edit session func TestTaskEdit_SaveAndContinue(t *testing.T) { ta := testutil.NewTestApp(t) defer ta.Cleanup() // Create task taskID := "TIKI-1" if err := testutil.CreateTestTask(ta.TaskDir, taskID, "Original Title", taskpkg.StatusReady, taskpkg.TypeStory); err != nil { t.Fatalf("failed to create task: %v", err) } if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } // Navigate: Board → Task Detail → Edit ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil) ta.Draw() ta.SendKey(tcell.KeyEnter, 0, tcell.ModNone) ta.SendKey(tcell.KeyRune, 'e', tcell.ModNone) // Edit and save ta.SendKeyToFocused(tcell.KeyCtrlL, 0, tcell.ModNone) // Select all ta.SendText("New Title") ta.SendKey(tcell.KeyCtrlS, 0, tcell.ModNone) // Verify save worked if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } task := ta.TaskStore.GetTask(taskID) if task == nil { t.Fatalf("task not found") return } if task.Title != "New Title" { t.Errorf("title = %q, want %q", task.Title, "New Title") } } // TestTaskEdit_EscapeAndReEdit verifies clean state after cancel and re-edit func TestTaskEdit_EscapeAndReEdit(t *testing.T) { ta := testutil.NewTestApp(t) defer ta.Cleanup() // Create task taskID := "TIKI-1" originalTitle := "Original Title" if err := testutil.CreateTestTask(ta.TaskDir, taskID, originalTitle, taskpkg.StatusReady, taskpkg.TypeStory); err != nil { t.Fatalf("failed to create task: %v", err) } if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } // Navigate: Board → Task Detail ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil) ta.Draw() ta.SendKey(tcell.KeyEnter, 0, tcell.ModNone) // Start edit and make changes ta.SendKey(tcell.KeyRune, 'e', tcell.ModNone) ta.SendKeyToFocused(tcell.KeyCtrlL, 0, tcell.ModNone) ta.SendText("Changed Title") // Cancel with Escape ta.SendKey(tcell.KeyEscape, 0, tcell.ModNone) // Verify editing state cleared if ta.EditingTask() != nil { t.Errorf("editing task should be nil after cancel") } // Start edit again ta.SendKey(tcell.KeyEscape, 0, tcell.ModNone) ta.SendKey(tcell.KeyEnter, 0, tcell.ModNone) ta.SendKey(tcell.KeyRune, 'e', tcell.ModNone) // Verify field shows original title (clean state) // Make actual change and save ta.SendKeyToFocused(tcell.KeyCtrlL, 0, tcell.ModNone) ta.SendText("New Title") ta.SendKey(tcell.KeyCtrlS, 0, tcell.ModNone) // Reload and verify if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } task := ta.TaskStore.GetTask(taskID) if task == nil { t.Fatalf("task not found") return } if task.Title != "New Title" { t.Errorf("title = %q, want %q", task.Title, "New Title") } } // TestTaskEdit_PriorityRange verifies priority can be set to valid values func TestTaskEdit_PriorityRange(t *testing.T) { ta := testutil.NewTestApp(t) defer ta.Cleanup() // Create task taskID := "TIKI-1" if err := testutil.CreateTestTask(ta.TaskDir, taskID, "Test Task", taskpkg.StatusReady, taskpkg.TypeStory); err != nil { t.Fatalf("failed to create task: %v", err) } if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } // Navigate: Board → Task Detail → Task Edit ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil) ta.Draw() ta.SendKey(tcell.KeyEnter, 0, tcell.ModNone) ta.SendKey(tcell.KeyRune, 'e', tcell.ModNone) // Tab to Priority field (Title → Status → Type → Priority) for i := 0; i < 3; i++ { ta.SendKey(tcell.KeyTab, 0, tcell.ModNone) } // Current priority is 3 (from fixture) // Press Down twice to get to priority 5 ta.SendKeyToFocused(tcell.KeyDown, 0, tcell.ModNone) ta.SendKeyToFocused(tcell.KeyDown, 0, tcell.ModNone) // Save ta.SendKey(tcell.KeyCtrlS, 0, tcell.ModNone) // Reload and verify if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } task := ta.TaskStore.GetTask(taskID) if task == nil { t.Fatalf("task not found") return } if task.Priority != 5 { t.Errorf("priority = %d, want 5", task.Priority) } } // TestTaskEdit_PointsRange verifies points can be set to valid values func TestTaskEdit_PointsRange(t *testing.T) { ta := testutil.NewTestApp(t) defer ta.Cleanup() // Create task taskID := "TIKI-1" if err := testutil.CreateTestTask(ta.TaskDir, taskID, "Test Task", taskpkg.StatusReady, taskpkg.TypeStory); err != nil { t.Fatalf("failed to create task: %v", err) } if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } // Navigate: Board → Task Detail → Task Edit ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil) ta.Draw() ta.SendKey(tcell.KeyEnter, 0, tcell.ModNone) ta.SendKey(tcell.KeyRune, 'e', tcell.ModNone) // Tab to Points field (Title → Status → Type → Priority → Points) for i := 0; i < 4; i++ { ta.SendKey(tcell.KeyTab, 0, tcell.ModNone) } // Current points is 1 (from fixture) // Default maxPoints is 10, so valid range is [1, 10] // Press Up 6 times to get to 7 (1→2→3→4→5→6→7) for i := 0; i < 6; i++ { ta.SendKeyToFocused(tcell.KeyUp, 0, tcell.ModNone) } // Save ta.SendKey(tcell.KeyCtrlS, 0, tcell.ModNone) // Reload and verify if err := ta.TaskStore.Reload(); err != nil { t.Fatalf("failed to reload: %v", err) } task := ta.TaskStore.GetTask(taskID) if task == nil { t.Fatalf("task not found") return } if task.Points != 7 { t.Errorf("points = %d, want 7", task.Points) } // That's sufficient to verify points field cycling works // (Testing wrapping would require reopening the task which we're avoiding) }