mirror of
https://github.com/boolean-maybe/tiki
synced 2026-04-21 13:37:20 +00:00
377 lines
11 KiB
Go
377 lines
11 KiB
Go
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"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"
|
|
)
|
|
|
|
// TestTaskDeletion_FromKanban verifies 'd' deletes task from kanban
|
|
func TestTaskDeletion_FromKanban(t *testing.T) {
|
|
ta := testutil.NewTestApp(t)
|
|
defer ta.Cleanup()
|
|
|
|
// Load plugins to enable Kanban
|
|
if err := ta.LoadPlugins(); err != nil {
|
|
t.Fatalf("failed to load plugins: %v", err)
|
|
}
|
|
|
|
// Create tasks
|
|
if err := testutil.CreateTestTask(ta.TaskDir, "TIKI-1", "First Task", taskpkg.StatusReady, taskpkg.TypeStory); err != nil {
|
|
t.Fatalf("failed to create task: %v", err)
|
|
}
|
|
if err := testutil.CreateTestTask(ta.TaskDir, "TIKI-2", "Second 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 to Kanban plugin
|
|
ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil)
|
|
ta.Draw()
|
|
|
|
// Verify TIKI-1 visible
|
|
found, _, _ := ta.FindText("TIKI-1")
|
|
if !found {
|
|
ta.DumpScreen()
|
|
t.Fatalf("TIKI-1 should be visible before delete")
|
|
}
|
|
|
|
// Press 'd' to delete first task
|
|
ta.SendKey(tcell.KeyRune, 'd', tcell.ModNone)
|
|
|
|
// Reload and verify task deleted
|
|
if err := ta.TaskStore.Reload(); err != nil {
|
|
t.Fatalf("failed to reload: %v", err)
|
|
}
|
|
|
|
task := ta.TaskStore.GetTask("TIKI-1")
|
|
if task != nil {
|
|
t.Errorf("TIKI-1 should be deleted from store")
|
|
}
|
|
|
|
// Verify file removed
|
|
taskPath := filepath.Join(ta.TaskDir, "tiki-1.md")
|
|
if _, err := os.Stat(taskPath); !os.IsNotExist(err) {
|
|
t.Errorf("TIKI-1 file should be deleted")
|
|
}
|
|
|
|
// Verify TIKI-2 still visible
|
|
found2, _, _ := ta.FindText("TIKI-2")
|
|
if !found2 {
|
|
ta.DumpScreen()
|
|
t.Errorf("TIKI-2 should still be visible after deleting TIKI-1")
|
|
}
|
|
}
|
|
|
|
// TestTaskDeletion_SelectionMoves verifies selection moves to next task after delete
|
|
func TestTaskDeletion_SelectionMoves(t *testing.T) {
|
|
ta := testutil.NewTestApp(t)
|
|
defer ta.Cleanup()
|
|
|
|
// Load plugins to enable Kanban
|
|
if err := ta.LoadPlugins(); err != nil {
|
|
t.Fatalf("failed to load plugins: %v", err)
|
|
}
|
|
|
|
// Create three tasks
|
|
if err := testutil.CreateTestTask(ta.TaskDir, "TIKI-1", "First Task", taskpkg.StatusReady, taskpkg.TypeStory); err != nil {
|
|
t.Fatalf("failed to create task: %v", err)
|
|
}
|
|
if err := testutil.CreateTestTask(ta.TaskDir, "TIKI-2", "Second Task", taskpkg.StatusReady, taskpkg.TypeStory); err != nil {
|
|
t.Fatalf("failed to create task: %v", err)
|
|
}
|
|
if err := testutil.CreateTestTask(ta.TaskDir, "TIKI-3", "Third 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 to Kanban plugin
|
|
ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil)
|
|
ta.Draw()
|
|
|
|
// Move to second task (index 1)
|
|
ta.SendKey(tcell.KeyDown, 0, tcell.ModNone)
|
|
|
|
kanbanConfig := ta.GetPluginConfig("Kanban")
|
|
// Verify we're on index 1
|
|
if kanbanConfig.GetSelectedIndex() != 1 {
|
|
t.Fatalf("expected index 1, got %d", kanbanConfig.GetSelectedIndex())
|
|
}
|
|
|
|
// Delete TIKI-2
|
|
ta.SendKey(tcell.KeyRune, 'd', tcell.ModNone)
|
|
|
|
// Selection should move to next task (TIKI-3, which is now at index 1)
|
|
selectedIndex := kanbanConfig.GetSelectedIndex()
|
|
if selectedIndex != 1 {
|
|
t.Errorf("selection after delete = index %d, want index 1", selectedIndex)
|
|
}
|
|
|
|
// Verify TIKI-3 is visible
|
|
found3, _, _ := ta.FindText("TIKI-3")
|
|
if !found3 {
|
|
ta.DumpScreen()
|
|
t.Errorf("TIKI-3 should be visible after deleting TIKI-2")
|
|
}
|
|
}
|
|
|
|
// TestTaskDeletion_LastTaskInLane verifies deleting last task resets selection
|
|
func TestTaskDeletion_LastTaskInLane(t *testing.T) {
|
|
ta := testutil.NewTestApp(t)
|
|
defer ta.Cleanup()
|
|
|
|
// Load plugins to enable Kanban
|
|
if err := ta.LoadPlugins(); err != nil {
|
|
t.Fatalf("failed to load plugins: %v", err)
|
|
}
|
|
|
|
// Create only one task in todo lane
|
|
if err := testutil.CreateTestTask(ta.TaskDir, "TIKI-1", "Only 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 to Kanban plugin
|
|
ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil)
|
|
ta.Draw()
|
|
|
|
// Verify TIKI-1 visible
|
|
found, _, _ := ta.FindText("TIKI-1")
|
|
if !found {
|
|
ta.DumpScreen()
|
|
t.Fatalf("TIKI-1 should be visible")
|
|
}
|
|
|
|
// Delete the only task
|
|
ta.SendKey(tcell.KeyRune, 'd', tcell.ModNone)
|
|
|
|
// Reload
|
|
if err := ta.TaskStore.Reload(); err != nil {
|
|
t.Fatalf("failed to reload: %v", err)
|
|
}
|
|
|
|
// Verify task deleted
|
|
task := ta.TaskStore.GetTask("TIKI-1")
|
|
if task != nil {
|
|
t.Errorf("TIKI-1 should be deleted")
|
|
}
|
|
|
|
kanbanConfig := ta.GetPluginConfig("Kanban")
|
|
// Verify selection reset to 0
|
|
if kanbanConfig.GetSelectedIndex() != 0 {
|
|
t.Errorf("selection should reset to 0 after deleting last task, got %d", kanbanConfig.GetSelectedIndex())
|
|
}
|
|
|
|
// Verify no crash occurred (lane is empty)
|
|
// This is implicit - if we got here without panic, test passes
|
|
}
|
|
|
|
// TestTaskDeletion_MultipleSequential verifies deleting multiple tasks in sequence
|
|
func TestTaskDeletion_MultipleSequential(t *testing.T) {
|
|
ta := testutil.NewTestApp(t)
|
|
defer ta.Cleanup()
|
|
|
|
// Load plugins to enable Kanban
|
|
if err := ta.LoadPlugins(); err != nil {
|
|
t.Fatalf("failed to load plugins: %v", err)
|
|
}
|
|
|
|
// Create five tasks
|
|
for i := 1; i <= 5; i++ {
|
|
taskID := fmt.Sprintf("TIKI-%d", i)
|
|
title := fmt.Sprintf("Task %d", i)
|
|
if err := testutil.CreateTestTask(ta.TaskDir, taskID, 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 to Kanban plugin
|
|
ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil)
|
|
ta.Draw()
|
|
|
|
// Delete first task
|
|
ta.SendKey(tcell.KeyRune, 'd', tcell.ModNone)
|
|
|
|
// Delete first task again (was TIKI-2, now at top)
|
|
ta.SendKey(tcell.KeyRune, 'd', tcell.ModNone)
|
|
|
|
// Delete first task again (was TIKI-3, now at top)
|
|
ta.SendKey(tcell.KeyRune, 'd', tcell.ModNone)
|
|
|
|
// Reload and verify only 2 tasks remain
|
|
if err := ta.TaskStore.Reload(); err != nil {
|
|
t.Fatalf("failed to reload: %v", err)
|
|
}
|
|
|
|
allTasks := ta.TaskStore.GetAllTasks()
|
|
if len(allTasks) != 2 {
|
|
t.Errorf("expected 2 tasks remaining, got %d", len(allTasks))
|
|
}
|
|
|
|
// Verify TIKI-4 and TIKI-5 still exist
|
|
task4 := ta.TaskStore.GetTask("TIKI-4")
|
|
task5 := ta.TaskStore.GetTask("TIKI-5")
|
|
if task4 == nil || task5 == nil {
|
|
t.Errorf("TIKI-4 and TIKI-5 should still exist")
|
|
}
|
|
}
|
|
|
|
// TestTaskDeletion_FromDifferentLane verifies deleting from non-todo lane
|
|
func TestTaskDeletion_FromDifferentLane(t *testing.T) {
|
|
ta := testutil.NewTestApp(t)
|
|
defer ta.Cleanup()
|
|
|
|
// Load plugins to enable Kanban
|
|
if err := ta.LoadPlugins(); err != nil {
|
|
t.Fatalf("failed to load plugins: %v", err)
|
|
}
|
|
|
|
// Create task in in_progress lane
|
|
if err := testutil.CreateTestTask(ta.TaskDir, "TIKI-1", "In Progress Task", taskpkg.StatusInProgress, 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 to Kanban plugin
|
|
ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil)
|
|
ta.Draw()
|
|
|
|
// Move to in_progress lane (Right arrow)
|
|
ta.SendKey(tcell.KeyRight, 0, tcell.ModNone)
|
|
|
|
// Verify TIKI-1 visible
|
|
found, _, _ := ta.FindText("TIKI-1")
|
|
if !found {
|
|
ta.DumpScreen()
|
|
t.Fatalf("TIKI-1 should be visible in in_progress lane")
|
|
}
|
|
|
|
// Delete task
|
|
ta.SendKey(tcell.KeyRune, 'd', tcell.ModNone)
|
|
|
|
// Reload and verify deleted
|
|
if err := ta.TaskStore.Reload(); err != nil {
|
|
t.Fatalf("failed to reload: %v", err)
|
|
}
|
|
|
|
task := ta.TaskStore.GetTask("TIKI-1")
|
|
if task != nil {
|
|
t.Errorf("TIKI-1 should be deleted")
|
|
}
|
|
}
|
|
|
|
// TestTaskDeletion_CannotDeleteFromTaskDetail verifies 'd' doesn't work in task detail
|
|
func TestTaskDeletion_CannotDeleteFromTaskDetail(t *testing.T) {
|
|
ta := testutil.NewTestApp(t)
|
|
defer ta.Cleanup()
|
|
|
|
// Load plugins to enable Kanban
|
|
if err := ta.LoadPlugins(); err != nil {
|
|
t.Fatalf("failed to load plugins: %v", err)
|
|
}
|
|
|
|
// Create task
|
|
if err := testutil.CreateTestTask(ta.TaskDir, "TIKI-1", "Task to Not Delete", 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: Kanban → Task Detail
|
|
ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil)
|
|
ta.Draw()
|
|
ta.SendKey(tcell.KeyEnter, 0, tcell.ModNone)
|
|
|
|
// Verify we're on task detail
|
|
currentView := ta.NavController.CurrentView()
|
|
if currentView.ViewID != model.TaskDetailViewID {
|
|
t.Fatalf("expected task detail view, got %v", currentView.ViewID)
|
|
}
|
|
|
|
// Press 'd' (should not delete from task detail view)
|
|
ta.SendKey(tcell.KeyRune, 'd', tcell.ModNone)
|
|
|
|
// Reload and verify task still exists
|
|
if err := ta.TaskStore.Reload(); err != nil {
|
|
t.Fatalf("failed to reload: %v", err)
|
|
}
|
|
|
|
task := ta.TaskStore.GetTask("TIKI-1")
|
|
if task == nil {
|
|
t.Errorf("TIKI-1 should NOT be deleted from task detail view")
|
|
}
|
|
|
|
// Verify we're still on task detail (or moved somewhere else, but task exists)
|
|
if task == nil {
|
|
t.Errorf("task should still exist after pressing 'd' in task detail")
|
|
}
|
|
}
|
|
|
|
// TestTaskDeletion_WithMultipleLanes verifies deletion doesn't affect other lanes
|
|
func TestTaskDeletion_WithMultipleLanes(t *testing.T) {
|
|
ta := testutil.NewTestApp(t)
|
|
defer ta.Cleanup()
|
|
|
|
// Load plugins to enable Kanban
|
|
if err := ta.LoadPlugins(); err != nil {
|
|
t.Fatalf("failed to load plugins: %v", err)
|
|
}
|
|
|
|
// Create tasks in different lanes
|
|
if err := testutil.CreateTestTask(ta.TaskDir, "TIKI-1", "Todo Task", taskpkg.StatusReady, taskpkg.TypeStory); err != nil {
|
|
t.Fatalf("failed to create task: %v", err)
|
|
}
|
|
if err := testutil.CreateTestTask(ta.TaskDir, "TIKI-2", "In Progress Task", taskpkg.StatusInProgress, taskpkg.TypeStory); err != nil {
|
|
t.Fatalf("failed to create task: %v", err)
|
|
}
|
|
if err := testutil.CreateTestTask(ta.TaskDir, "TIKI-3", "Done Task", taskpkg.StatusDone, 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 to Kanban plugin
|
|
ta.NavController.PushView(model.MakePluginViewID("Kanban"), nil)
|
|
ta.Draw()
|
|
|
|
// Delete TIKI-1 from todo lane
|
|
ta.SendKey(tcell.KeyRune, 'd', tcell.ModNone)
|
|
|
|
// Reload
|
|
if err := ta.TaskStore.Reload(); err != nil {
|
|
t.Fatalf("failed to reload: %v", err)
|
|
}
|
|
|
|
// Verify TIKI-1 deleted
|
|
if ta.TaskStore.GetTask("TIKI-1") != nil {
|
|
t.Errorf("TIKI-1 should be deleted")
|
|
}
|
|
|
|
// Verify TIKI-2 and TIKI-3 still exist (in other lanes)
|
|
if ta.TaskStore.GetTask("TIKI-2") == nil {
|
|
t.Errorf("TIKI-2 (in different lane) should still exist")
|
|
}
|
|
if ta.TaskStore.GetTask("TIKI-3") == nil {
|
|
t.Errorf("TIKI-3 (in different lane) should still exist")
|
|
}
|
|
}
|