v0.3.0 ea9abc7

destination_test.go RawHistoryFolder

1package destination
2
3import (
4	"os"
5	"path/filepath"
6	"regexp"
7	"strings"
8	"testing"
9
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/require"
12)
13
14// env gives each test an isolated home and config with no XDG overrides.
15func env(t *testing.T) (home string) {
16	t.Helper()
17	home = t.TempDir()
18	t.Setenv("HOME", home)
19	t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, "cfg"))
20	t.Setenv("XDG_PICTURES_DIR", "")
21	t.Setenv("XDG_VIDEOS_DIR", "")
22	return home
23}
24
25func writeDirs(t *testing.T, content string) {
26	t.Helper()
27	cfg := os.Getenv("XDG_CONFIG_HOME")
28	require.NoError(t, os.MkdirAll(cfg, 0o700))
29	require.NoError(t, os.WriteFile(filepath.Join(cfg, "user-dirs.dirs"), []byte(content), 0o600))
30}
31
32var nameRE = regexp.MustCompile(`^nefercap-\d{8}-\d{6}\.\d{9}\.(png|mp4)$`)
33
34func TestFallbackDefaults(t *testing.T) {
35	home := env(t)
36
37	shot, err := Screenshot()
38	require.NoError(t, err)
39	assert.Equal(t, filepath.Join(home, "Pictures", "Screenshots"), filepath.Dir(shot))
40	assert.Regexp(t, nameRE, filepath.Base(shot))
41	assert.Equal(t, ".png", filepath.Ext(shot))
42
43	vid, err := Video()
44	require.NoError(t, err)
45	assert.Equal(t, filepath.Join(home, "Videos"), filepath.Dir(vid))
46	assert.Equal(t, ".mp4", filepath.Ext(vid))
47	assert.Regexp(t, nameRE, filepath.Base(vid))
48
49	for _, d := range []string{filepath.Dir(shot), filepath.Dir(vid)} {
50		fi, err := os.Stat(d)
51		require.NoError(t, err)
52		assert.True(t, fi.IsDir())
53	}
54	_, err = os.Lstat(shot)
55	assert.True(t, os.IsNotExist(err), "the file itself is not created")
56}
57
58func TestDirectoryMode(t *testing.T) {
59	home := env(t)
60	old := umaskZero()
61	defer old()
62	shot, err := Screenshot()
63	require.NoError(t, err)
64	fi, err := os.Stat(filepath.Dir(shot))
65	require.NoError(t, err)
66	assert.Equal(t, os.FileMode(0o700), fi.Mode().Perm())
67	fi, err = os.Stat(filepath.Join(home, "Pictures"))
68	require.NoError(t, err)
69	assert.Equal(t, os.FileMode(0o700), fi.Mode().Perm())
70}
71
72func TestEnvironmentDirectories(t *testing.T) {
73	home := env(t)
74	pics := t.TempDir()
75	vids := filepath.Join(home, "Movies")
76	t.Setenv("XDG_PICTURES_DIR", pics)
77	t.Setenv("XDG_VIDEOS_DIR", "$HOME/Movies")
78
79	shot, err := Screenshot()
80	require.NoError(t, err)
81	assert.Equal(t, filepath.Join(pics, "Screenshots"), filepath.Dir(shot))
82
83	vid, err := Video()
84	require.NoError(t, err)
85	assert.Equal(t, vids, filepath.Dir(vid))
86
87	t.Setenv("XDG_VIDEOS_DIR", "${HOME}/Clips/../Movies2")
88	vid, err = Video()
89	require.NoError(t, err)
90	assert.Equal(t, filepath.Join(home, "Movies2"), filepath.Dir(vid))
91}
92
93func TestConfigFile(t *testing.T) {
94	home := env(t)
95	writeDirs(t, `# comment
96XDG_DESKTOP_DIR="$HOME/Desktop"
97XDG_PICTURES_DIR="$HOME/My \"Pics\""
98XDG_VIDEOS_DIR="$HOME/Films"
99`)
100	shot, err := Screenshot()
101	require.NoError(t, err)
102	assert.Equal(t, filepath.Join(home, `My "Pics"`, "Screenshots"), filepath.Dir(shot))
103	vid, err := Video()
104	require.NoError(t, err)
105	assert.Equal(t, filepath.Join(home, "Films"), filepath.Dir(vid))
106
107	// The environment variable wins over the file.
108	other := t.TempDir()
109	t.Setenv("XDG_VIDEOS_DIR", other)
110	vid, err = Video()
111	require.NoError(t, err)
112	assert.Equal(t, other, filepath.Dir(vid))
113}
114
115func TestConfigFileDefaultLocation(t *testing.T) {
116	home := env(t)
117	t.Setenv("XDG_CONFIG_HOME", "")
118	require.NoError(t, os.MkdirAll(filepath.Join(home, ".config"), 0o700))
119	require.NoError(t, os.WriteFile(filepath.Join(home, ".config", "user-dirs.dirs"),
120		[]byte("XDG_VIDEOS_DIR=\"$HOME/Reels\"\n"), 0o600))
121	vid, err := Video()
122	require.NoError(t, err)
123	assert.Equal(t, filepath.Join(home, "Reels"), filepath.Dir(vid))
124}
125
126func TestUnsafeConfiguredValuesFallBack(t *testing.T) {
127	home := env(t)
128	for _, v := range []string{
129		"relative/dir",
130		"$OTHER/dir",
131		"$(id)/x",
132		"`id`",
133		"$HOME", // xdg-user-dirs uses HOME to mean "disabled"
134		"$HOME/",
135		"/",
136		"$HOMEDIR/x",
137	} {
138		t.Setenv("XDG_VIDEOS_DIR", v)
139		vid, err := Video()
140		require.NoError(t, err, v)
141		assert.Equal(t, filepath.Join(home, "Videos"), filepath.Dir(vid), v)
142	}
143	// Same for the file, including an unterminated quote.
144	t.Setenv("XDG_VIDEOS_DIR", "")
145	writeDirs(t, "XDG_VIDEOS_DIR=\"$HOME/Broken\n")
146	vid, err := Video()
147	require.NoError(t, err)
148	assert.Equal(t, filepath.Join(home, "Videos"), filepath.Dir(vid))
149	// Nothing outside the home was created.
150	_, err = os.Lstat(filepath.Join(home, "Broken"))
151	assert.True(t, os.IsNotExist(err))
152}
153
154func TestOversizedConfigIgnored(t *testing.T) {
155	home := env(t)
156	big := make([]byte, maxConfig)
157	for i := range big {
158		big[i] = '#'
159	}
160	writeDirs(t, string(big)+"\nXDG_VIDEOS_DIR=\"$HOME/Late\"\n")
161	vid, err := Video()
162	require.NoError(t, err)
163	assert.Equal(t, filepath.Join(home, "Videos"), filepath.Dir(vid))
164}
165
166func TestConfiguredNonDirectoryFails(t *testing.T) {
167	env(t)
168	file := filepath.Join(t.TempDir(), "f")
169	require.NoError(t, os.WriteFile(file, nil, 0o600))
170	t.Setenv("XDG_VIDEOS_DIR", file)
171	_, err := Video()
172	assert.Error(t, err)
173	t.Setenv("XDG_VIDEOS_DIR", filepath.Join(file, "sub"))
174	_, err = Video()
175	assert.Error(t, err)
176}
177
178func TestNoHome(t *testing.T) {
179	t.Setenv("HOME", "")
180	t.Setenv("XDG_PICTURES_DIR", "")
181	_, err := Screenshot()
182	assert.ErrorIs(t, err, ErrNoHome)
183	t.Setenv("HOME", "relative")
184	_, err = Video()
185	assert.ErrorIs(t, err, ErrNoHome)
186}
187
188func TestNamesDoNotCollide(t *testing.T) {
189	env(t)
190	seen := map[string]bool{}
191	for i := 0; i < 200; i++ {
192		p, err := Screenshot()
193		require.NoError(t, err)
194		seen[p] = true
195	}
196	assert.Len(t, seen, 200)
197}
198
199func TestInExplicitDirectory(t *testing.T) {
200	root := filepath.Join(t.TempDir(), "a", "b")
201	shot, err := ScreenshotIn(root)
202	require.NoError(t, err)
203	assert.Equal(t, root, filepath.Dir(shot))
204	assert.True(t, strings.HasSuffix(shot, ".png"))
205	fi, err := os.Stat(root)
206	require.NoError(t, err)
207	assert.Equal(t, os.FileMode(0o700), fi.Mode().Perm())
208	vid, err := VideoIn(root)
209	require.NoError(t, err)
210	assert.Equal(t, root, filepath.Dir(vid))
211	assert.True(t, strings.HasSuffix(vid, ".mp4"))
212}

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 2

Tags 9

↑↓ moveenter openesc close