package taskwarrior import ( "testing" ) func TestBuildTaskTree_EmptyList(t *testing.T) { tasks := Tasks{} tree := BuildTaskTree(tasks) if tree == nil { t.Fatal("Expected tree to be non-nil") } if len(tree.Nodes) != 0 { t.Errorf("Expected 0 nodes, got %d", len(tree.Nodes)) } if len(tree.Roots) != 0 { t.Errorf("Expected 0 roots, got %d", len(tree.Roots)) } if len(tree.FlatList) != 0 { t.Errorf("Expected 0 items in flat list, got %d", len(tree.FlatList)) } } func TestBuildTaskTree_NoParents(t *testing.T) { tasks := Tasks{ {Uuid: "task1", Description: "Task 1", Status: "pending"}, {Uuid: "task2", Description: "Task 2", Status: "pending"}, {Uuid: "task3", Description: "Task 3", Status: "completed"}, } tree := BuildTaskTree(tasks) if len(tree.Nodes) != 3 { t.Errorf("Expected 3 nodes, got %d", len(tree.Nodes)) } if len(tree.Roots) != 3 { t.Errorf("Expected 3 roots (all tasks have no parent), got %d", len(tree.Roots)) } if len(tree.FlatList) != 3 { t.Errorf("Expected 3 items in flat list, got %d", len(tree.FlatList)) } // All tasks should have depth 0 for i, node := range tree.FlatList { if node.Depth != 0 { t.Errorf("Task %d expected depth 0, got %d", i, node.Depth) } if node.HasChildren() { t.Errorf("Task %d should not have children", i) } } } func TestBuildTaskTree_SimpleParentChild(t *testing.T) { tasks := Tasks{ {Uuid: "parent1", Description: "Parent Task", Status: "pending"}, { Uuid: "child1", Description: "Child Task 1", Status: "pending", Udas: map[string]any{"parenttask": "parent1"}, }, { Uuid: "child2", Description: "Child Task 2", Status: "completed", Udas: map[string]any{"parenttask": "parent1"}, }, } tree := BuildTaskTree(tasks) if len(tree.Nodes) != 3 { t.Fatalf("Expected 3 nodes, got %d", len(tree.Nodes)) } if len(tree.Roots) != 1 { t.Fatalf("Expected 1 root, got %d", len(tree.Roots)) } // Check root is the parent root := tree.Roots[0] if root.Task.Uuid != "parent1" { t.Errorf("Expected root to be parent1, got %s", root.Task.Uuid) } // Check parent has 2 children if len(root.Children) != 2 { t.Fatalf("Expected parent to have 2 children, got %d", len(root.Children)) } // Check children status completed, total := root.GetChildrenStatus() if total != 2 { t.Errorf("Expected total children = 2, got %d", total) } if completed != 1 { t.Errorf("Expected completed children = 1, got %d", completed) } // Check flat list order (parent first, then children) if len(tree.FlatList) != 3 { t.Fatalf("Expected 3 items in flat list, got %d", len(tree.FlatList)) } if tree.FlatList[0].Task.Uuid != "parent1" { t.Errorf("Expected first item to be parent, got %s", tree.FlatList[0].Task.Uuid) } if tree.FlatList[0].Depth != 0 { t.Errorf("Expected parent depth = 0, got %d", tree.FlatList[0].Depth) } // Children should be at depth 1 for i := 1; i < 3; i++ { if tree.FlatList[i].Depth != 1 { t.Errorf("Expected child %d depth = 1, got %d", i, tree.FlatList[i].Depth) } if tree.FlatList[i].Parent == nil { t.Errorf("Child %d should have a parent", i) } else if tree.FlatList[i].Parent.Task.Uuid != "parent1" { t.Errorf("Child %d parent should be parent1, got %s", i, tree.FlatList[i].Parent.Task.Uuid) } } } func TestBuildTaskTree_MultiLevel(t *testing.T) { tasks := Tasks{ {Uuid: "grandparent", Description: "Grandparent", Status: "pending"}, { Uuid: "parent1", Description: "Parent 1", Status: "pending", Udas: map[string]any{"parenttask": "grandparent"}, }, { Uuid: "parent2", Description: "Parent 2", Status: "pending", Udas: map[string]any{"parenttask": "grandparent"}, }, { Uuid: "child1", Description: "Child 1", Status: "pending", Udas: map[string]any{"parenttask": "parent1"}, }, { Uuid: "grandchild1", Description: "Grandchild 1", Status: "completed", Udas: map[string]any{"parenttask": "child1"}, }, } tree := BuildTaskTree(tasks) if len(tree.Nodes) != 5 { t.Fatalf("Expected 5 nodes, got %d", len(tree.Nodes)) } if len(tree.Roots) != 1 { t.Fatalf("Expected 1 root, got %d", len(tree.Roots)) } // Find nodes by UUID grandparentNode := tree.Nodes["grandparent"] parent1Node := tree.Nodes["parent1"] child1Node := tree.Nodes["child1"] grandchildNode := tree.Nodes["grandchild1"] // Check depths if grandparentNode.Depth != 0 { t.Errorf("Expected grandparent depth = 0, got %d", grandparentNode.Depth) } if parent1Node.Depth != 1 { t.Errorf("Expected parent1 depth = 1, got %d", parent1Node.Depth) } if child1Node.Depth != 2 { t.Errorf("Expected child1 depth = 2, got %d", child1Node.Depth) } if grandchildNode.Depth != 3 { t.Errorf("Expected grandchild depth = 3, got %d", grandchildNode.Depth) } // Check parent-child relationships if len(grandparentNode.Children) != 2 { t.Errorf("Expected grandparent to have 2 children, got %d", len(grandparentNode.Children)) } if len(parent1Node.Children) != 1 { t.Errorf("Expected parent1 to have 1 child, got %d", len(parent1Node.Children)) } if len(child1Node.Children) != 1 { t.Errorf("Expected child1 to have 1 child, got %d", len(child1Node.Children)) } if grandchildNode.HasChildren() { t.Error("Expected grandchild to have no children") } // Check flat list maintains tree order if len(tree.FlatList) != 5 { t.Fatalf("Expected 5 items in flat list, got %d", len(tree.FlatList)) } // Grandparent should be first if tree.FlatList[0].Task.Uuid != "grandparent" { t.Errorf("Expected first item to be grandparent, got %s", tree.FlatList[0].Task.Uuid) } } func TestBuildTaskTree_OrphanedTask(t *testing.T) { tasks := Tasks{ {Uuid: "task1", Description: "Normal Task", Status: "pending"}, { Uuid: "orphan", Description: "Orphaned Task", Status: "pending", Udas: map[string]any{"parenttask": "nonexistent"}, }, } tree := BuildTaskTree(tasks) if len(tree.Nodes) != 2 { t.Fatalf("Expected 2 nodes, got %d", len(tree.Nodes)) } // Orphaned task should be treated as root if len(tree.Roots) != 2 { t.Errorf("Expected 2 roots (orphan should be treated as root), got %d", len(tree.Roots)) } // Both should have depth 0 for _, node := range tree.FlatList { if node.Depth != 0 { t.Errorf("Expected all tasks to have depth 0, got %d for %s", node.Depth, node.Task.Description) } } } func TestTaskNode_GetChildrenStatus(t *testing.T) { tests := []struct { name string children []*TaskNode wantComp int wantTotal int }{ { name: "no children", children: []*TaskNode{}, wantComp: 0, wantTotal: 0, }, { name: "all pending", children: []*TaskNode{ {Task: &Task{Status: "pending"}}, {Task: &Task{Status: "pending"}}, }, wantComp: 0, wantTotal: 2, }, { name: "all completed", children: []*TaskNode{ {Task: &Task{Status: "completed"}}, {Task: &Task{Status: "completed"}}, {Task: &Task{Status: "completed"}}, }, wantComp: 3, wantTotal: 3, }, { name: "mixed status", children: []*TaskNode{ {Task: &Task{Status: "completed"}}, {Task: &Task{Status: "pending"}}, {Task: &Task{Status: "completed"}}, {Task: &Task{Status: "pending"}}, {Task: &Task{Status: "completed"}}, }, wantComp: 3, wantTotal: 5, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { node := &TaskNode{ Task: &Task{}, Children: tt.children, } gotComp, gotTotal := node.GetChildrenStatus() if gotComp != tt.wantComp { t.Errorf("GetChildrenStatus() completed = %d, want %d", gotComp, tt.wantComp) } if gotTotal != tt.wantTotal { t.Errorf("GetChildrenStatus() total = %d, want %d", gotTotal, tt.wantTotal) } }) } } func TestTaskNode_HasChildren(t *testing.T) { tests := []struct { name string children []*TaskNode want bool }{ { name: "no children", children: []*TaskNode{}, want: false, }, { name: "has children", children: []*TaskNode{{Task: &Task{}}}, want: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { node := &TaskNode{ Task: &Task{}, Children: tt.children, } if got := node.HasChildren(); got != tt.want { t.Errorf("HasChildren() = %v, want %v", got, tt.want) } }) } }