v0.31.0 bf0756b

workspace_close_test.go RawHistoryFolder

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_InvalidPreconditions(t *testing.T) {
63	tests := []struct {
64		name        string
65		closingPane *entity.PaneNode
66		wantText    string
67	}{
68		{
69			name:        "nil closing pane",
70			closingPane: nil,
71			wantText:    "closing pane missing",
72		},
73		{
74			name:        "closing pane has no parent",
75			closingPane: testLeafNode("closing"),
76			wantText:    "closing pane has no parent",
77		},
78		{
79			name: "parent is not split",
80			closingPane: func() *entity.PaneNode {
81				closing := testLeafNode("closing")
82				parent := &entity.PaneNode{ID: "parent", Children: []*entity.PaneNode{closing}}
83				closing.Parent = parent
84				return closing
85			}(),
86			wantText: "parent node is not split",
87		},
88	}
89
90	for _, tt := range tests {
91		t.Run(tt.name, func(t *testing.T) {
92			_, err := deriveIncrementalCloseTreeContext(tt.closingPane)
93			if err == nil {
94				t.Fatalf("expected error")
95			}
96			if !strings.Contains(err.Error(), tt.wantText) {
97				t.Fatalf("expected %q error, got: %v", tt.wantText, err)
98			}
99		})
100	}
101}
102
103func TestDeriveIncrementalCloseTreeContext_MissingSibling(t *testing.T) {
104	closing := testLeafNode("closing")
105	parent := testSplitNode("parent", closing, nil)
106
107	_, err := deriveIncrementalCloseTreeContext(closing)
108	if err == nil {
109		t.Fatalf("expected invariant error")
110	}
111	if !strings.Contains(err.Error(), "sibling") && !strings.Contains(err.Error(), "nil child") {
112		t.Fatalf("expected sibling/nil-child error, got: %v", err)
113	}
114
115	if got := paneNodeID(parent); got != "parent" {
116		t.Fatalf("unexpected parent id helper output: %s", got)
117	}
118}
119
120func TestDeriveIncrementalCloseTreeContext_ParentDoesNotContainClosingPane(t *testing.T) {
121	closing := testLeafNode("closing")
122	left := testLeafNode("left")
123	right := testLeafNode("right")
124	parent := testSplitNode("parent", left, right)
125	closing.Parent = parent
126
127	_, err := deriveIncrementalCloseTreeContext(closing)
128	if err == nil {
129		t.Fatalf("expected invariant error")
130	}
131	if !strings.Contains(err.Error(), "not found under parent") {
132		t.Fatalf("unexpected error: %v", err)
133	}
134}
135
136func TestDeriveIncrementalCloseTreeContext_ParentMissingFromGrandparent(t *testing.T) {
137	closing := testLeafNode("closing")
138	sibling := testLeafNode("sibling")
139	parent := testSplitNode("parent", closing, sibling)
140	grand := testSplitNode("grand", testLeafNode("other-left"), testLeafNode("other-right"))
141	parent.Parent = grand
142
143	_, err := deriveIncrementalCloseTreeContext(closing)
144	if err == nil {
145		t.Fatalf("expected invariant error")
146	}
147	if !strings.Contains(err.Error(), "parent not found under grandparent") {
148		t.Fatalf("unexpected error: %v", err)
149	}
150}
151
152func TestCaptureIncrementalCloseContext_MissingSiblingSetsPrecheckReason(t *testing.T) {
153	closing := testLeafNode("closing")
154	testSplitNode("parent", closing, nil)
155
156	coord := &WorkspaceCoordinator{}
157	ctx := coord.captureIncrementalCloseContext(nil, closing)
158	if ctx.precheckReason == "" {
159		t.Fatalf("expected precheck reason")
160	}
161}
162
163func TestDeriveIncrementalCloseTreeContext_ConcurrentPaneAndTabCloseSnapshots(t *testing.T) {
164	paneCloseNode := testLeafNode("pane-close")
165	paneCloseSibling := testLeafNode("pane-close-sibling")
166	testSplitNode("pane-parent", paneCloseNode, paneCloseSibling)
167
168	tabCloseNode := testLeafNode("tab-close")
169	tabCloseSibling := testLeafNode("tab-close-sibling")
170	testSplitNode("tab-parent", tabCloseSibling, tabCloseNode)
171
172	run := func(node *entity.PaneNode, wg *sync.WaitGroup, errCh chan<- error) {
173		defer wg.Done()
174		for range 200 {
175			if _, err := deriveIncrementalCloseTreeContext(node); err != nil {
176				errCh <- err
177				return
178			}
179		}
180	}
181
182	errCh := make(chan error, 2)
183	var wg sync.WaitGroup
184	wg.Add(2)
185	go run(paneCloseNode, &wg, errCh)
186	go run(tabCloseNode, &wg, errCh)
187	wg.Wait()
188	close(errCh)
189
190	for err := range errCh {
191		if err != nil {
192			t.Fatalf("unexpected concurrent derive error: %v", err)
193		}
194	}
195}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close