main ece7682

dir_test.go RawHistoryFolder

1package platform
2
3import (
4	"os"
5	"path/filepath"
6	"strings"
7	"testing"
8)
9
10func TestActivateDevelopmentEnvironment(t *testing.T) {
11	originalCwd, err := os.Getwd()
12	if err != nil {
13		t.Fatal(err)
14	}
15	t.Cleanup(func() {
16		if err := os.Chdir(originalCwd); err != nil {
17			t.Errorf("restore working directory: %v", err)
18		}
19	})
20
21	distinctCwd := t.TempDir()
22	tests := []struct {
23		name        string
24		environment string
25		cwd         string
26		wantErr     bool
27		check       func(*testing.T, string)
28	}{
29		{
30			name: "unset preserves existing XDG roots",
31			check: func(t *testing.T, _ string) {
32				t.Helper()
33				if got := os.Getenv("XDG_CONFIG_HOME"); got != "/existing/config" {
34					t.Errorf("XDG_CONFIG_HOME = %q", got)
35				}
36				if got := os.Getenv("XDG_STATE_HOME"); got != "/existing/state" {
37					t.Errorf("XDG_STATE_HOME = %q", got)
38				}
39				if got := os.Getenv("XDG_RUNTIME_DIR"); got != "/existing/runtime" {
40					t.Errorf("XDG_RUNTIME_DIR = %q", got)
41				}
42			},
43		},
44		{
45			name:        "valid name overrides XDG roots",
46			environment: "dev",
47			check: func(t *testing.T, cwd string) {
48				t.Helper()
49				root := filepath.Join(cwd, ".dev", "dev")
50				if got := os.Getenv("XDG_CONFIG_HOME"); got != filepath.Join(root, "config") {
51					t.Errorf("XDG_CONFIG_HOME = %q", got)
52				}
53				if got := os.Getenv("XDG_STATE_HOME"); got != filepath.Join(root, "state") {
54					t.Errorf("XDG_STATE_HOME = %q", got)
55				}
56				if got := os.Getenv("XDG_RUNTIME_DIR"); got != filepath.Join(root, "runtime") {
57					t.Errorf("XDG_RUNTIME_DIR = %q", got)
58				}
59				if got, want := os.Getenv("VEV_ENV_ROOT"), filepath.Join(cwd, ".dev"); got != want {
60					t.Errorf("VEV_ENV_ROOT = %q, want %q", got, want)
61				}
62				if _, err := os.Stat(root); !os.IsNotExist(err) {
63					t.Errorf("activation created root or returned unexpected error: %v", err)
64				}
65			},
66		},
67		{
68			name:        "resolved root survives cwd change",
69			environment: "persisted",
70			check: func(t *testing.T, cwd string) {
71				t.Helper()
72				root := filepath.Join(cwd, ".dev", "persisted")
73				other := t.TempDir()
74				if err := os.Chdir(other); err != nil {
75					t.Fatal(err)
76				}
77				if err := ActivateDevelopmentEnvironment(); err != nil {
78					t.Fatal(err)
79				}
80				if got := os.Getenv("XDG_STATE_HOME"); got != filepath.Join(root, "state") {
81					t.Errorf("XDG_STATE_HOME after cwd change = %q", got)
82				}
83			},
84		},
85		{name: "distinct name alpha", environment: "alpha", cwd: distinctCwd},
86		{name: "distinct name beta", environment: "beta", cwd: distinctCwd},
87		{name: "reject traversal", environment: "../live", wantErr: true},
88		{name: "reject separator", environment: "a/b", wantErr: true},
89		{name: "reject whitespace", environment: "two words", wantErr: true},
90		{name: "reject leading punctuation", environment: ".hidden", wantErr: true},
91		{name: "reject 65 characters", environment: strings.Repeat("a", 65), wantErr: true},
92	}
93
94	for _, tt := range tests {
95		t.Run(tt.name, func(t *testing.T) {
96			cwd := tt.cwd
97			if cwd == "" {
98				cwd = t.TempDir()
99			}
100			if err := os.Chdir(cwd); err != nil {
101				t.Fatal(err)
102			}
103			canonicalCwd, getwdErr := os.Getwd()
104			if getwdErr != nil {
105				t.Fatal(getwdErr)
106			}
107			t.Setenv("VEV_ENV", tt.environment)
108			t.Setenv("VEV_ENV_ROOT", "")
109			t.Setenv("XDG_CONFIG_HOME", "/existing/config")
110			t.Setenv("XDG_STATE_HOME", "/existing/state")
111			t.Setenv("XDG_RUNTIME_DIR", "/existing/runtime")
112
113			err := ActivateDevelopmentEnvironment()
114			if tt.wantErr {
115				if err == nil || !strings.Contains(err.Error(), "VEV_ENV") {
116					t.Fatalf("ActivateDevelopmentEnvironment() error = %v", err)
117				}
118				for name, want := range map[string]string{
119					"XDG_CONFIG_HOME": "/existing/config",
120					"XDG_STATE_HOME":  "/existing/state",
121					"XDG_RUNTIME_DIR": "/existing/runtime",
122					"VEV_ENV_ROOT":    "",
123				} {
124					if got := os.Getenv(name); got != want {
125						t.Errorf("%s changed to %q, want %q", name, got, want)
126					}
127				}
128				return
129			}
130			if err != nil {
131				t.Fatal(err)
132			}
133			if tt.check != nil {
134				tt.check(t, canonicalCwd)
135			}
136		})
137	}
138}
139
140func TestDevelopmentEnvironmentTempDir(t *testing.T) {
141	absoluteBase := filepath.Join(t.TempDir(), ".dev")
142	tests := []struct {
143		name        string
144		environment string
145		base        string
146		want        string
147		wantActive  bool
148	}{
149		{name: "inactive without environment", base: absoluteBase},
150		{name: "inactive without inherited base", environment: "work"},
151		{
152			name:        "active environment",
153			environment: "work",
154			base:        absoluteBase,
155			want:        filepath.Join(absoluteBase, "work", "tmp"),
156			wantActive:  true,
157		},
158	}
159
160	for _, tt := range tests {
161		t.Run(tt.name, func(t *testing.T) {
162			t.Setenv("VEV_ENV", tt.environment)
163			t.Setenv("VEV_ENV_ROOT", tt.base)
164
165			got, active := DevelopmentEnvironmentTempDir()
166			if got != tt.want || active != tt.wantActive {
167				t.Fatalf("DevelopmentEnvironmentTempDir() = (%q, %v), want (%q, %v)", got, active, tt.want, tt.wantActive)
168			}
169			if active && !filepath.IsAbs(got) {
170				t.Errorf("DevelopmentEnvironmentTempDir() = %q, want absolute path", got)
171			}
172		})
173	}
174}
175
176func TestActivateDevelopmentEnvironmentRecomputesProfileFromInheritedBase(t *testing.T) {
177	originalCwd, err := os.Getwd()
178	if err != nil {
179		t.Fatal(err)
180	}
181	t.Cleanup(func() {
182		if err := os.Chdir(originalCwd); err != nil {
183			t.Errorf("restore working directory: %v", err)
184		}
185	})
186
187	if err := os.Chdir(t.TempDir()); err != nil {
188		t.Fatal(err)
189	}
190	invocationCwd, err := os.Getwd()
191	if err != nil {
192		t.Fatal(err)
193	}
194	t.Setenv("VEV_ENV", "alpha")
195	t.Setenv("VEV_ENV_ROOT", "")
196	if err := ActivateDevelopmentEnvironment(); err != nil {
197		t.Fatal(err)
198	}
199	base := filepath.Join(invocationCwd, ".dev")
200	if got := os.Getenv("VEV_ENV_ROOT"); got != base {
201		t.Fatalf("VEV_ENV_ROOT = %q, want %q", got, base)
202	}
203
204	if err := os.Chdir(t.TempDir()); err != nil {
205		t.Fatal(err)
206	}
207	if err := os.Setenv("VEV_ENV", "beta"); err != nil {
208		t.Fatal(err)
209	}
210	if err := ActivateDevelopmentEnvironment(); err != nil {
211		t.Fatal(err)
212	}
213
214	betaRoot := filepath.Join(base, "beta")
215	for name, want := range map[string]string{
216		"VEV_ENV_ROOT":    base,
217		"XDG_CONFIG_HOME": filepath.Join(betaRoot, "config"),
218		"XDG_STATE_HOME":  filepath.Join(betaRoot, "state"),
219		"XDG_RUNTIME_DIR": filepath.Join(betaRoot, "runtime"),
220	} {
221		if got := os.Getenv(name); got != want {
222			t.Errorf("%s = %q, want %q", name, got, want)
223		}
224	}
225}
226
227func TestStateDir(t *testing.T) {
228	tests := []struct {
229		name     string
230		xdgState string
231		home     string
232		want     func(*testing.T, string, string) string
233	}{
234		{
235			name:     "xdg state home",
236			xdgState: filepath.Join(t.TempDir(), "xdg-state"),
237			home:     filepath.Join(t.TempDir(), "home"),
238			want: func(_ *testing.T, xdgState, _ string) string {
239				return filepath.Join(xdgState, "vev")
240			},
241		},
242		{
243			name: "home fallback",
244			home: filepath.Join(t.TempDir(), "home"),
245			want: func(_ *testing.T, _, home string) string {
246				return filepath.Join(home, ".local", "state", "vev")
247			},
248		},
249		{
250			name: "temp fallback without home",
251			want: func(t *testing.T, _, _ string) string {
252				t.Helper()
253				return filepath.Join(os.TempDir(), "vev-state-")
254			},
255		},
256	}
257
258	for _, tt := range tests {
259		t.Run(tt.name, func(t *testing.T) {
260			t.Setenv("XDG_STATE_HOME", tt.xdgState)
261			t.Setenv("HOME", tt.home)
262
263			want := tt.want(t, tt.xdgState, tt.home)
264			got := StateDir()
265			if strings.HasSuffix(want, "vev-state-") {
266				if !strings.HasPrefix(got, want) {
267					t.Fatalf("StateDir() = %q, want prefix %q", got, want)
268				}
269				return
270			}
271			if got != want {
272				t.Fatalf("StateDir() = %q, want %q", got, want)
273			}
274		})
275	}
276}
277
278func TestConfigPath(t *testing.T) {
279	tests := []struct {
280		name      string
281		xdgConfig string
282		home      string
283		want      func(*testing.T, string, string) string
284	}{
285		{
286			name:      "xdg config home",
287			xdgConfig: filepath.Join(t.TempDir(), "xdg-config"),
288			home:      filepath.Join(t.TempDir(), "home"),
289			want: func(_ *testing.T, xdgConfig, _ string) string {
290				return filepath.Join(xdgConfig, "vev", "config")
291			},
292		},
293		{
294			name: "home fallback",
295			home: filepath.Join(t.TempDir(), "home"),
296			want: func(_ *testing.T, _, home string) string {
297				return filepath.Join(home, ".config", "vev", "config")
298			},
299		},
300		{
301			name: "temp fallback without home",
302			want: func(t *testing.T, _, _ string) string {
303				t.Helper()
304				return filepath.Join(os.TempDir(), "vev-config-")
305			},
306		},
307	}
308
309	for _, tt := range tests {
310		t.Run(tt.name, func(t *testing.T) {
311			t.Setenv("XDG_CONFIG_HOME", tt.xdgConfig)
312			t.Setenv("HOME", tt.home)
313
314			want := tt.want(t, tt.xdgConfig, tt.home)
315			got := ConfigPath()
316			if strings.HasSuffix(want, "vev-config-") {
317				if !strings.HasPrefix(got, want) || filepath.Base(got) != "config" {
318					t.Fatalf("ConfigPath() = %q, want prefix %q and base config", got, want)
319				}
320				return
321			}
322			if got != want {
323				t.Fatalf("ConfigPath() = %q, want %q", got, want)
324			}
325		})
326	}
327}
328
329func TestDirOrHome(t *testing.T) {
330	existing := t.TempDir()
331	home := filepath.Join(t.TempDir(), "home")
332	nonexistent := filepath.Join(t.TempDir(), "missing")
333
334	tests := []struct {
335		name string
336		dir  string
337		home string
338		want string
339	}{
340		{name: "existing dir", dir: existing, home: home, want: existing},
341		{name: "non-existent dir falls back to home", dir: nonexistent, home: home, want: home},
342		{name: "non-existent dir without home falls back to root", dir: nonexistent, want: "/"},
343	}
344
345	for _, tt := range tests {
346		t.Run(tt.name, func(t *testing.T) {
347			t.Setenv("HOME", tt.home)
348
349			if got := DirOrHome(tt.dir); got != tt.want {
350				t.Fatalf("DirOrHome(%q) = %q, want %q", tt.dir, got, tt.want)
351			}
352		})
353	}
354}

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