v0.28.0 3cb3770

omnibox_navigation_state_test.go RawHistoryFolder

1package component
2
3import (
4	"testing"
5
6	"github.com/bnema/puregotk/v4/gdk"
7)
8
9func TestShouldPreferTypedURLNavigation(t *testing.T) {
10	tests := []struct {
11		name  string
12		input string
13		want  bool
14	}{
15		{name: "domain", input: "x.com", want: true},
16		{name: "domain with path", input: "github.com/bnema", want: true},
17		{name: "http url", input: "http://example.com", want: true},
18		{name: "localhost", input: "localhost", want: true},
19		{name: "ipv4", input: "127.0.0.1", want: true},
20		{name: "ipv4 with port", input: "192.168.1.1:8080", want: true},
21		{name: "ipv6 bracketed", input: "[::1]", want: true},
22		{name: "http ipv6 bracketed", input: "http://[::1]", want: true},
23		{name: "search query", input: "hello world", want: false},
24		{name: "empty", input: "", want: false},
25	}
26
27	for _, tt := range tests {
28		t.Run(tt.name, func(t *testing.T) {
29			if got := shouldPreferTypedURLNavigation(tt.input); got != tt.want {
30				t.Fatalf("shouldPreferTypedURLNavigation(%q) = %v, want %v", tt.input, got, tt.want)
31			}
32		})
33	}
34}
35
36func TestResetSearchSessionState(t *testing.T) {
37	o := &Omnibox{}
38	o.lastQuery = "github"
39
40	o.resetSearchSessionState()
41
42	if o.lastQuery != "" {
43		t.Fatalf("lastQuery should be reset, got %q", o.lastQuery)
44	}
45}
46
47func TestIsDeletionKey(t *testing.T) {
48	tests := []struct {
49		name string
50		key  uint
51		want bool
52	}{
53		{name: "backspace is deletion", key: uint(gdk.KEY_BackSpace), want: true},
54		{name: "delete is deletion", key: uint(gdk.KEY_Delete), want: true},
55		{name: "typing key is not deletion", key: uint(gdk.KEY_g), want: false},
56		{name: "arrow key is not deletion", key: uint(gdk.KEY_Left), want: false},
57	}
58
59	for _, tt := range tests {
60		t.Run(tt.name, func(t *testing.T) {
61			if got := isDeletionKey(tt.key); got != tt.want {
62				t.Fatalf("isDeletionKey(%d) = %v, want %v", tt.key, got, tt.want)
63			}
64		})
65	}
66}
67
68func TestEffectiveSearchQuery(t *testing.T) {
69	tests := []struct {
70		name      string
71		entryText string
72		realInput string
73		hasGhost  bool
74		want      string
75	}{
76		{name: "uses typed input when ghost visible", entryText: "dumber.bnema.dev", realInput: "dumb", hasGhost: true, want: "dumb"},
77		{name: "falls back to entry when no ghost", entryText: "dumb", realInput: "dumb", hasGhost: false, want: "dumb"},
78		{name: "uses entry when real input unavailable", entryText: "dumb", realInput: "", hasGhost: true, want: "dumb"},
79	}
80
81	for _, tt := range tests {
82		t.Run(tt.name, func(t *testing.T) {
83			if got := effectiveSearchQuery(tt.entryText, tt.realInput, tt.hasGhost); got != tt.want {
84				t.Fatalf("effectiveSearchQuery(%q, %q, %v) = %q, want %q", tt.entryText, tt.realInput, tt.hasGhost, got, tt.want)
85			}
86		})
87	}
88}
89
90func TestUpdateGhostFromSelectionUsesRealInputWhenGhostVisible(t *testing.T) {
91	entryText := "github.com/bnema/dumber"
92	realInput := "upl"
93
94	got := effectiveSearchQuery(entryText, realInput, true)
95	if got != realInput {
96		t.Fatalf("effectiveSearchQuery should prefer real input when ghost is visible, got %q want %q", got, realInput)
97	}
98}
99
100func TestResolveTargetURLForSelection(t *testing.T) {
101	suggestions := []Suggestion{
102		{URL: "https://github.com/bnema/dumber"},
103		{URL: "https://github.com/bnema/dumber/pulls"},
104	}
105	favorites := []Favorite{
106		{URL: "https://dumber.bnema.dev"},
107	}
108
109	tests := []struct {
110		name    string
111		mode    ViewMode
112		index   int
113		wantURL string
114	}{
115		{name: "history index", mode: ViewModeHistory, index: 1, wantURL: "https://github.com/bnema/dumber/pulls"},
116		{name: "favorites index", mode: ViewModeFavorites, index: 0, wantURL: "https://dumber.bnema.dev"},
117		{name: "invalid index", mode: ViewModeHistory, index: 99, wantURL: ""},
118	}
119
120	for _, tt := range tests {
121		t.Run(tt.name, func(t *testing.T) {
122			got := resolveTargetURLForSelection(tt.mode, tt.index, suggestions, favorites)
123			if got != tt.wantURL {
124				t.Fatalf("resolveTargetURLForSelection(%s, %d) = %q, want %q", tt.mode, tt.index, got, tt.wantURL)
125			}
126		})
127	}
128}
129
130func TestSelectedTargetURL(t *testing.T) {
131	suggestions := []Suggestion{
132		{URL: "https://github.com/bnema/dumber"},
133	}
134	favorites := []Favorite{
135		{URL: "https://dumber.bnema.dev"},
136	}
137
138	tests := []struct {
139		name     string
140		mode     ViewMode
141		index    int
142		wantURL  string
143		wantBool bool
144	}{
145		{name: "negative index is not explicit selection", mode: ViewModeHistory, index: -1, wantURL: "", wantBool: false},
146		{name: "history selection is explicit", mode: ViewModeHistory, index: 0, wantURL: "https://github.com/bnema/dumber", wantBool: true},
147		{name: "favorites selection is explicit", mode: ViewModeFavorites, index: 0, wantURL: "https://dumber.bnema.dev", wantBool: true},
148	}
149
150	for _, tt := range tests {
151		t.Run(tt.name, func(t *testing.T) {
152			gotURL, gotBool := selectedTargetURL(tt.mode, tt.index, suggestions, favorites)
153			if gotURL != tt.wantURL || gotBool != tt.wantBool {
154				t.Fatalf("selectedTargetURL(%s, %d) = (%q, %v), want (%q, %v)", tt.mode, tt.index, gotURL, gotBool, tt.wantURL, tt.wantBool)
155			}
156		})
157	}
158}
159
160func TestVisibleGhostSuggestion(t *testing.T) {
161	suggestions := []Suggestion{
162		{URL: "https://github.com/bnema/dumber"},
163		{URL: "https://gitlab.com/team/project"},
164	}
165
166	tests := []struct {
167		name                 string
168		input                string
169		selectedURL          string
170		hasExplicitSelection bool
171		wantFull             string
172		wantSuffix           string
173		wantOK               bool
174	}{
175		{
176			name:       "top visible candidate drives ghost without selection",
177			input:      "git",
178			wantFull:   "github.com",
179			wantSuffix: "hub.com",
180			wantOK:     true,
181		},
182		{
183			name:                 "explicit selection wins over top candidate",
184			input:                "git",
185			selectedURL:          "https://gitlab.com/team/project",
186			hasExplicitSelection: true,
187			wantFull:             "gitlab.com/team/project",
188			wantSuffix:           "lab.com/team/project",
189			wantOK:               true,
190		},
191		{
192			name:       "no visible prefix match means no ghost",
193			input:      "xyz",
194			wantFull:   "",
195			wantSuffix: "",
196			wantOK:     false,
197		},
198	}
199
200	for _, tt := range tests {
201		t.Run(tt.name, func(t *testing.T) {
202			gotFull, gotSuffix, gotOK := visibleGhostSuggestion(
203				tt.input,
204				tt.selectedURL,
205				tt.hasExplicitSelection,
206				ViewModeHistory,
207				suggestions,
208				nil,
209			)
210			if gotFull != tt.wantFull || gotSuffix != tt.wantSuffix || gotOK != tt.wantOK {
211				t.Fatalf(
212					"visibleGhostSuggestion(%q) = (%q, %q, %v), want (%q, %q, %v)",
213					tt.input,
214					gotFull,
215					gotSuffix,
216					gotOK,
217					tt.wantFull,
218					tt.wantSuffix,
219					tt.wantOK,
220				)
221			}
222		})
223	}
224}
225
226func TestShouldPromoteHoverSelection(t *testing.T) {
227	tests := []struct {
228		name         string
229		realInput    string
230		hasGhostText bool
231		hasNavigated bool
232		want         bool
233	}{
234		{
235			name: "initial open allows hover selection",
236			want: true,
237		},
238		{
239			name:      "typed input keeps hover from stealing authority",
240			realInput: "git",
241			want:      false,
242		},
243		{
244			name:         "visible ghost keeps hover from stealing authority",
245			hasGhostText: true,
246			want:         false,
247		},
248		{
249			name:         "keyboard navigation keeps hover from stealing authority",
250			hasNavigated: true,
251			want:         false,
252		},
253	}
254
255	for _, tt := range tests {
256		t.Run(tt.name, func(t *testing.T) {
257			got := shouldPromoteHoverSelection(tt.realInput, tt.hasGhostText, tt.hasNavigated)
258			if got != tt.want {
259				t.Fatalf(
260					"shouldPromoteHoverSelection(%q, %v, %v) = %v, want %v",
261					tt.realInput,
262					tt.hasGhostText,
263					tt.hasNavigated,
264					got,
265					tt.want,
266				)
267			}
268		})
269	}
270}

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