1package browser
2
3import (
4 "testing"
5
6 "github.com/diamondburned/gotk4/pkg/gtk/v4"
7)
8
9// TestFocusNeighborVerticalSplit tests basic tree structure validation
10// Note: Full geometry-based navigation testing requires integration tests with real GTK widgets
11func TestFocusNeighborVerticalSplit(t *testing.T) {
12 t.Skip("Structural neighbor navigation requires real GTK widgets for geometry calculations")
13}
14
15// TestFocusNeighborHorizontalSplit tests basic tree structure validation
16// Note: Full geometry-based navigation testing requires integration tests with real GTK widgets
17func TestFocusNeighborHorizontalSplit(t *testing.T) {
18 t.Skip("Structural neighbor navigation requires real GTK widgets for geometry calculations")
19}
20
21// TestPaneNodeStructure verifies basic pane node relationships
22func TestPaneNodeStructure(t *testing.T) {
23 root := &paneNode{
24 orientation: gtk.OrientationVertical,
25 isLeaf: false,
26 }
27
28 child1 := &paneNode{
29 parent: root,
30 isLeaf: true,
31 }
32
33 child2 := &paneNode{
34 parent: root,
35 isLeaf: true,
36 }
37
38 root.left = child1
39 root.right = child2
40
41 // Verify parent-child relationships
42 if child1.parent != root {
43 t.Errorf("child1.parent should be root")
44 }
45
46 if child2.parent != root {
47 t.Errorf("child2.parent should be root")
48 }
49
50 // Verify root children
51 if root.left != child1 {
52 t.Errorf("root.left should be child1")
53 }
54
55 if root.right != child2 {
56 t.Errorf("root.right should be child2")
57 }
58
59 // Verify leaf status
60 if !child1.isLeaf {
61 t.Errorf("child1 should be a leaf")
62 }
63
64 if !child2.isLeaf {
65 t.Errorf("child2 should be a leaf")
66 }
67
68 if root.isLeaf {
69 t.Errorf("root should not be a leaf")
70 }
71}
72
73// TestMapDirection verifies direction mapping to GTK orientation
74func TestMapDirection(t *testing.T) {
75 tests := []struct {
76 direction string
77 orientation gtk.Orientation
78 existingFirst bool
79 }{
80 {DirectionDown, gtk.OrientationVertical, true},
81 {DirectionUp, gtk.OrientationVertical, false},
82 {DirectionRight, gtk.OrientationHorizontal, true},
83 {DirectionLeft, gtk.OrientationHorizontal, false},
84 }
85
86 for _, tt := range tests {
87 t.Run(tt.direction, func(t *testing.T) {
88 orientation, existingFirst := mapDirection(tt.direction)
89
90 if orientation != tt.orientation {
91 t.Errorf("mapDirection(%q) orientation = %v, want %v",
92 tt.direction, orientation, tt.orientation)
93 }
94
95 if existingFirst != tt.existingFirst {
96 t.Errorf("mapDirection(%q) existingFirst = %v, want %v",
97 tt.direction, existingFirst, tt.existingFirst)
98 }
99 })
100 }
101}
102
103// TestInvalidDirections verifies handling of invalid directions
104func TestInvalidDirections(t *testing.T) {
105 wm := &WorkspaceManager{}
106
107 invalidDirections := []string{
108 "",
109 "unknown",
110 "diagonal",
111 }
112
113 for _, direction := range invalidDirections {
114 t.Run(direction, func(t *testing.T) {
115 success := wm.FocusNeighbor(direction)
116 if success {
117 t.Errorf("Expected FocusNeighbor(%q) to return false, got true", direction)
118 }
119 })
120 }
121}
122
123// TestCaseSensitiveDirections verifies that direction strings are case-insensitive
124func TestCaseSensitiveDirections(t *testing.T) {
125 wm := &WorkspaceManager{}
126
127 // These should be accepted (case insensitive), but will return false
128 // because there's no active pane to navigate from
129 validDirections := []string{
130 "UP",
131 "Down",
132 "LEFT",
133 "Right",
134 DirectionUp,
135 DirectionDown,
136 DirectionLeft,
137 DirectionRight,
138 }
139
140 for _, direction := range validDirections {
141 t.Run(direction, func(t *testing.T) {
142 // Should not panic or error, just return false since no panes exist
143 _ = wm.FocusNeighbor(direction)
144 })
145 }
146}
147
148// TestNavigationCoordinateBug documents the coordinate space bug in navigation
149//
150// Bug Description:
151// When comparing positions for geometric navigation, the code mixes two coordinate spaces:
152// - Normal panes: return window-absolute coordinates from WidgetGetAllocation()
153// - Stacked panes: return stack-box-relative coordinates from WidgetGetAllocation()
154//
155// This causes navigation to fail because we're comparing coordinates in different spaces.
156//
157// Real-world example from logs:
158// Normal pane (top half): center=(957, 532) - window coords
159// Stack pane (bottom): center=(955, 511) - relative to stack box!
160// Result: dy=-21 → SKIPPED as "not below"
161//
162// The fix: Always use stack wrapper allocation for panes inside stacks.
163func TestNavigationCoordinateBug(t *testing.T) {
164 // Simulate the coordinate math from real user scenario
165 // Window: 1914x1064, vertical split: normal pane (top) + stack (bottom)
166
167 t.Run("BugValidation_CoordinateMixing", func(t *testing.T) {
168 // Normal pane occupies top half - window-absolute coords
169 normalCX, normalCY := 957.0, 266.0
170
171 // Stack pane child returns RELATIVE coordinates (bug!)
172 // Its allocation is relative to stack box at y=532
173 stackChildCX, stackChildCY := 957.0, 133.0 // Relative to parent!
174
175 dy := stackChildCY - normalCY
176 t.Logf("Normal pane: center=(%.0f, %.0f)", normalCX, normalCY)
177 t.Logf("Stack child (buggy relative): center=(%.0f, %.0f)", stackChildCX, stackChildCY)
178 t.Logf("dy = %.0f (negative = appears above)", dy)
179
180 // Navigation DOWN requires dy > 0, but we get negative!
181 const focusEpsilon = 0.5
182 if dy <= focusEpsilon {
183 t.Logf("✓ BUG: dy=%.0f, navigation DOWN would FAIL", dy)
184 }
185 })
186
187 t.Run("CorrectBehavior_UseStackWrapper", func(t *testing.T) {
188 // Normal pane (same as before)
189 normalCX, normalCY := 957.0, 266.0
190
191 // Stack wrapper has correct window-absolute coordinates
192 stackWrapperCX, stackWrapperCY := 957.0, 798.0
193
194 dy := stackWrapperCY - normalCY
195 t.Logf("Normal pane: center=(%.0f, %.0f)", normalCX, normalCY)
196 t.Logf("Stack wrapper (correct): center=(%.0f, %.0f)", stackWrapperCX, stackWrapperCY)
197 t.Logf("dy = %.0f (positive = correctly below)", dy)
198
199 const focusEpsilon = 0.5
200 if dy > focusEpsilon {
201 t.Logf("✓ CORRECT: Navigation DOWN would SUCCEED")
202 }
203
204 // Also verify UP navigation
205 dyUp := normalCY - stackWrapperCY
206 if dyUp < -focusEpsilon {
207 t.Logf("✓ CORRECT: Navigation UP would SUCCEED")
208 }
209 })
210}
211
212// TestGetNavigationAllocation tests the proposed fix helper function
213// This will be uncommented and updated after implementing the helper
214func TestGetNavigationAllocation(t *testing.T) {
215 t.Skip("Enable this test after implementing getNavigationAllocation() helper")
216
217 // TODO: After implementing getNavigationAllocation(), test:
218 // 1. Regular leaf pane → returns pane.container allocation
219 // 2. Pane inside stack → returns stack.stackWrapper allocation
220 // 3. Stack container itself → returns stack.stackWrapper allocation
221 // 4. Nil safety: node=nil, container=nil, parent=nil, stackWrapper=nil
222}