cross_focus_test.go RawHistoryFolder

1package layout
2
3import (
4	"testing"
5
6	"github.com/bnema/vev/internal/domain"
7	"github.com/stretchr/testify/require"
8)
9
10func TestFocusSpan(t *testing.T) {
11	t.Parallel()
12
13	area := domain.Rect{X: 5, Y: 7, Width: 41, Height: 5}
14	tests := []struct {
15		name    string
16		tree    *Tree
17		area    domain.Rect
18		want    domain.Rect
19		wantErr error
20	}{
21		{
22			name:    "nil root",
23			tree:    &Tree{},
24			area:    area,
25			wantErr: ErrNotFound,
26		},
27		{
28			name:    "layout too small",
29			tree:    NewTree("a"),
30			area:    domain.Rect{Width: MinPaneCols - 1, Height: MinPaneRows},
31			wantErr: ErrTooSmall,
32		},
33		{
34			name:    "focused pane unknown",
35			tree:    &Tree{Root: NewLeaf("a"), Focus: "missing"},
36			area:    area,
37			wantErr: ErrNotFound,
38		},
39		{
40			name: "returns collapsed stack title bar",
41			tree: &Tree{
42				Root:  &Node{Kind: Stack, Children: []*Node{NewLeaf("a"), NewLeaf("b")}, Expanded: "a"},
43				Focus: "b",
44			},
45			area: area,
46			want: domain.Rect{X: 5, Y: 11, Width: 41, Height: 1},
47		},
48	}
49
50	for _, tt := range tests {
51		t.Run(tt.name, func(t *testing.T) {
52			got, err := tt.tree.FocusSpan(tt.area)
53			require.ErrorIs(t, err, tt.wantErr)
54			require.Equal(t, tt.want, got)
55		})
56	}
57}
58
59func TestEntryPane(t *testing.T) {
60	t.Parallel()
61
62	area := domain.Rect{Width: 41, Height: 5}
63	tests := []struct {
64		name    string
65		tree    *Tree
66		dir     Direction
67		span    domain.Rect
68		area    domain.Rect
69		want    PaneID
70		wantErr error
71	}{
72		{
73			name:    "nil root",
74			tree:    &Tree{},
75			dir:     Right,
76			area:    area,
77			wantErr: ErrNotFound,
78		},
79		{
80			name:    "layout too small",
81			tree:    NewTree("a"),
82			dir:     Right,
83			area:    domain.Rect{Width: MinPaneCols - 1, Height: MinPaneRows},
84			wantErr: ErrTooSmall,
85		},
86		{
87			name:    "unknown direction has no pane",
88			tree:    NewTree("a"),
89			dir:     Direction(99),
90			area:    area,
91			wantErr: ErrNoPane,
92		},
93		{
94			name: "right enters left edge",
95			tree: &Tree{Root: &Node{Kind: Split, Dir: Horizontal, Children: []*Node{NewLeaf("left"), NewLeaf("right")}}, Focus: "right"},
96			dir:  Right,
97			span: domain.Rect{Y: 1, Width: 20, Height: 2},
98			area: area,
99			want: "left",
100		},
101		{
102			name: "left enters right edge",
103			tree: &Tree{Root: &Node{Kind: Split, Dir: Horizontal, Children: []*Node{NewLeaf("left"), NewLeaf("right")}}, Focus: "left"},
104			dir:  Left,
105			span: domain.Rect{Y: 1, Width: 20, Height: 2},
106			area: area,
107			want: "right",
108		},
109		{
110			name: "down enters top edge",
111			tree: &Tree{Root: &Node{Kind: Split, Dir: Vertical, Children: []*Node{NewLeaf("top"), NewLeaf("bottom")}}, Focus: "bottom"},
112			dir:  Down,
113			span: domain.Rect{X: 5, Width: 10, Height: 2},
114			area: area,
115			want: "top",
116		},
117		{
118			name: "up enters bottom edge",
119			tree: &Tree{Root: &Node{Kind: Split, Dir: Vertical, Children: []*Node{NewLeaf("top"), NewLeaf("bottom")}}, Focus: "top"},
120			dir:  Up,
121			span: domain.Rect{X: 5, Width: 10, Height: 2},
122			area: area,
123			want: "bottom",
124		},
125		{
126			name: "minimum edge gap precedes overlap",
127			tree: &Tree{Root: &Node{Kind: Split, Dir: Horizontal, Children: []*Node{
128				{Kind: Split, Dir: Vertical, Children: []*Node{NewLeaf("edge-top"), NewLeaf("edge-bottom")}},
129				NewLeaf("far"),
130			}}},
131			dir:  Right,
132			span: domain.Rect{Y: 1, Width: 20, Height: 4},
133			area: domain.Rect{Width: 62, Height: 5},
134			want: "edge-bottom",
135		},
136		{
137			name: "maximum perpendicular overlap breaks equal gap",
138			tree: &Tree{Root: &Node{Kind: Split, Dir: Vertical, Children: []*Node{NewLeaf("top"), NewLeaf("bottom")}}},
139			dir:  Right,
140			span: domain.Rect{Y: 3, Width: 20, Height: 2},
141			area: domain.Rect{Width: 20, Height: 5},
142			want: "bottom",
143		},
144		{
145			name: "pane id deterministically breaks full tie",
146			tree: &Tree{Root: &Node{Kind: Stack, Children: []*Node{NewLeaf("z"), NewLeaf("a")}, Expanded: "z"}},
147			dir:  Right,
148			span: domain.Rect{Y: 10, Width: 20, Height: 1},
149			area: domain.Rect{Width: 20, Height: 4},
150			want: "a",
151		},
152	}
153
154	for _, tt := range tests {
155		t.Run(tt.name, func(t *testing.T) {
156			before := tt.tree.Clone()
157			got, err := tt.tree.EntryPane(tt.dir, tt.span, tt.area)
158			require.ErrorIs(t, err, tt.wantErr)
159			require.Equal(t, tt.want, got)
160			require.Equal(t, before, tt.tree, "EntryPane must be pure")
161		})
162	}
163}
164
165func TestFocusEnterExpandsCollapsedStackMember(t *testing.T) {
166	t.Parallel()
167
168	area := domain.Rect{Width: 20, Height: 4}
169	tr := &Tree{
170		Root:  &Node{Kind: Stack, Children: []*Node{NewLeaf("expanded"), NewLeaf("collapsed")}, Expanded: "expanded"},
171		Focus: "expanded",
172	}
173
174	err := tr.FocusEnter(Right, domain.Rect{Y: 3, Width: 20, Height: 1}, area)
175	require.NoError(t, err)
176	require.Equal(t, PaneID("collapsed"), tr.Focus)
177	require.Equal(t, PaneID("collapsed"), tr.Root.Expanded)
178}

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 12

Tags 11

↑↓ moveenter openesc close