mirror of
https://github.com/boolean-maybe/tiki
synced 2026-04-21 13:37:20 +00:00
413 lines
12 KiB
Go
413 lines
12 KiB
Go
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)
|
|
}
|