1package coordinator
2
3import (
4 "strings"
5 "sync"
6 "testing"
7
8 "github.com/bnema/dumber/internal/domain/entity"
9)
10
11func testLeafNode(id string) *entity.PaneNode {
12 return &entity.PaneNode{
13 ID: id,
14 Pane: entity.NewPane(entity.PaneID(id)),
15 }
16}
17
18func testSplitNode(id string, left, right *entity.PaneNode) *entity.PaneNode {
19 node := &entity.PaneNode{
20 ID: id,
21 SplitDir: entity.SplitHorizontal,
22 Children: []*entity.PaneNode{left, right},
23 }
24 if left != nil {
25 left.Parent = node
26 }
27 if right != nil {
28 right.Parent = node
29 }
30 return node
31}
32
33func TestDeriveIncrementalCloseTreeContext_ValidSplit(t *testing.T) {
34 closing := testLeafNode("closing")
35 sibling := testLeafNode("sibling")
36 parent := testSplitNode("parent", closing, sibling)
37 other := testLeafNode("other")
38 grand := testSplitNode("grand", parent, other)
39
40 ctx, err := deriveIncrementalCloseTreeContext(closing)
41 if err != nil {
42 t.Fatalf("unexpected error: %v", err)
43 }
44
45 if ctx.parentNode != parent {
46 t.Fatalf("parent mismatch")
47 }
48 if ctx.siblingNode != sibling {
49 t.Fatalf("sibling mismatch")
50 }
51 if ctx.grandparentNode != grand {
52 t.Fatalf("grandparent mismatch")
53 }
54 if ctx.siblingIsStartChild {
55 t.Fatalf("sibling should be end child")
56 }
57 if !ctx.parentIsStartInGrand {
58 t.Fatalf("parent should be start child in grandparent")
59 }
60}
61
62func TestDeriveIncrementalCloseTreeContext_MissingSibling(t *testing.T) {
63 closing := testLeafNode("closing")
64 parent := testSplitNode("parent", closing, nil)
65
66 _, err := deriveIncrementalCloseTreeContext(closing)
67 if err == nil {
68 t.Fatalf("expected invariant error")
69 }
70 if !strings.Contains(err.Error(), "sibling") && !strings.Contains(err.Error(), "nil child") {
71 t.Fatalf("expected sibling/nil-child error, got: %v", err)
72 }
73
74 if got := paneNodeID(parent); got != "parent" {
75 t.Fatalf("unexpected parent id helper output: %s", got)
76 }
77}
78
79func TestDeriveIncrementalCloseTreeContext_ParentDoesNotContainClosingPane(t *testing.T) {
80 closing := testLeafNode("closing")
81 left := testLeafNode("left")
82 right := testLeafNode("right")
83 parent := testSplitNode("parent", left, right)
84 closing.Parent = parent
85
86 _, err := deriveIncrementalCloseTreeContext(closing)
87 if err == nil {
88 t.Fatalf("expected invariant error")
89 }
90 if !strings.Contains(err.Error(), "not found under parent") {
91 t.Fatalf("unexpected error: %v", err)
92 }
93}
94
95func TestCaptureIncrementalCloseContext_MissingSiblingSetsPrecheckReason(t *testing.T) {
96 closing := testLeafNode("closing")
97 testSplitNode("parent", closing, nil)
98
99 coord := &WorkspaceCoordinator{}
100 ctx := coord.captureIncrementalCloseContext(nil, closing)
101 if ctx.precheckReason == "" {
102 t.Fatalf("expected precheck reason")
103 }
104}
105
106func TestDeriveIncrementalCloseTreeContext_ConcurrentPaneAndTabCloseSnapshots(t *testing.T) {
107 paneCloseNode := testLeafNode("pane-close")
108 paneCloseSibling := testLeafNode("pane-close-sibling")
109 testSplitNode("pane-parent", paneCloseNode, paneCloseSibling)
110
111 tabCloseNode := testLeafNode("tab-close")
112 tabCloseSibling := testLeafNode("tab-close-sibling")
113 testSplitNode("tab-parent", tabCloseSibling, tabCloseNode)
114
115 run := func(node *entity.PaneNode, wg *sync.WaitGroup, errCh chan<- error) {
116 defer wg.Done()
117 for i := 0; i < 200; i++ {
118 if _, err := deriveIncrementalCloseTreeContext(node); err != nil {
119 errCh <- err
120 return
121 }
122 }
123 }
124
125 errCh := make(chan error, 2)
126 var wg sync.WaitGroup
127 wg.Add(2)
128 go run(paneCloseNode, &wg, errCh)
129 go run(tabCloseNode, &wg, errCh)
130 wg.Wait()
131 close(errCh)
132
133 for err := range errCh {
134 if err != nil {
135 t.Fatalf("unexpected concurrent derive error: %v", err)
136 }
137 }
138}