feat/data-dnd e05cb15

scalestore_test.go RawHistoryFolder

1package config
2
3import (
4	"math"
5	"os"
6	"path/filepath"
7	"testing"
8)
9
10func TestSaveOutputScale(t *testing.T) {
11	for _, tc := range []struct {
12		name, in, want string
13		scale          float64
14	}{
15		{"missing file", "", "output.DP-2.scale = 1.5\n", 1.5},
16		{"append", "# my config\nborder.width = 3", "# my config\nborder.width = 3\noutput.DP-2.scale = 1.5\n", 1.5},
17		{"replace in place", "output.DP-2.scale = 2 # big\n# end\n", "output.DP-2.scale = 1.25 # big\n# end\n", 1.25},
18		{"enable commented value", "# output.DP-2.scale = 4/3 # 4K display\nrender.vrr = on\n", "output.DP-2.scale = 1.5 # 4K display\nrender.vrr = on\n", 1.5},
19		{"enable indented CRLF example", "  # output.DP-2.scale = 2  # note\r\nrender.vrr = on\r\n", "  output.DP-2.scale = 1.5  # note\r\nrender.vrr = on\r\n", 1.5},
20		{"insert with same output group", "output.DP-2.hdr = off\noutput.DP-1 = off\n", "output.DP-2.hdr = off\noutput.DP-2.scale = 1.5\noutput.DP-1 = off\n", 1.5},
21		{"insert with output group", "output.DP-1 = off\nrender.vrr = on\n", "output.DP-1 = off\noutput.DP-2.scale = 1.5\nrender.vrr = on\n", 1.5},
22		{"enable example before bind", "# Scale setting\n# output.DP-2.scale = 4/3\n\nbind.cmd+return = spawn-terminal\n", "# Scale setting\noutput.DP-2.scale = 1.25\n\nbind.cmd+return = spawn-terminal\n", 1.25},
23		{"insert beside output", "output.DP-2 = preferred\noutput.DP-1 = off\nrender.vrr = on\n", "output.DP-2 = preferred\noutput.DP-2.scale = 1.5\noutput.DP-1 = off\nrender.vrr = on\n", 1.5},
24		{"active beats commented example", "output.DP-2.scale = 2\n# output.DP-2.scale = 4/3\n", "output.DP-2.scale = 1.5\n# output.DP-2.scale = 4/3\n", 1.5},
25		{"keeps CRLF", "output.DP-2.scale = 2\r\nborder.width = 3\r\n", "output.DP-2.scale = 1.5\r\nborder.width = 3\r\n", 1.5},
26		{"comment with = and #", "output.DP-2.scale = 2 # was a=b #1\n", "output.DP-2.scale = 1.5 # was a=b #1\n", 1.5},
27		{"keeps spacing", "output.DP-2.scale=2\n", "output.DP-2.scale=4/3\n", 4.0 / 3},
28		{"last line wins", "output.DP-2.scale = 2\noutput.DP-2.scale = 3\n", "output.DP-2.scale = 2\noutput.DP-2.scale = 5/3\n", 5.0 / 3},
29		{"ignores other outputs", "# output.DP-1.scale = 2\noutput.DP-1.scale = 2\n", "# output.DP-1.scale = 2\noutput.DP-1.scale = 2\noutput.DP-2.scale = 1\n", 1},
30	} {
31		t.Run(tc.name, func(t *testing.T) {
32			path := filepath.Join(t.TempDir(), "neferwl", "config")
33			if tc.in != "" {
34				if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
35					t.Fatal(err)
36				}
37				if err := os.WriteFile(path, []byte(tc.in), 0o600); err != nil {
38					t.Fatal(err)
39				}
40			}
41			if err := (ScaleStore{Path: path}).SaveOutputScale("DP-2", tc.scale); err != nil {
42				t.Fatal(err)
43			}
44			got, err := os.ReadFile(path)
45			if err != nil {
46				t.Fatal(err)
47			}
48			if string(got) != tc.want {
49				t.Fatalf("got\n%s\nwant\n%s", got, tc.want)
50			}
51			// The written value parses back to the same scale.
52			cfg, _, err := Load(path)
53			if err != nil {
54				t.Fatal(err)
55			}
56			found := false
57			for _, o := range cfg.Outputs {
58				if o.Name == "DP-2" {
59					found = true
60					if o.Scale != tc.scale {
61						t.Fatalf("parsed %v, want %v", o.Scale, tc.scale)
62					}
63				}
64			}
65			if !found {
66				t.Fatal("saved output missing from parsed config")
67			}
68			// Existing files keep their mode; new ones are private.
69			{
70				if info, _ := os.Stat(path); info.Mode().Perm() != 0o600 {
71					t.Fatalf("mode %v", info.Mode().Perm())
72				}
73			}
74		})
75	}
76}
77
78// A symlinked config (dotfiles) stays a symlink; its target gets the value.
79func TestSaveOutputScaleSymlink(t *testing.T) {
80	dir := t.TempDir()
81	target := filepath.Join(dir, "dotfiles-config")
82	if err := os.WriteFile(target, []byte("border.width = 3\n"), 0o644); err != nil {
83		t.Fatal(err)
84	}
85	link := filepath.Join(dir, "config")
86	if err := os.Symlink(target, link); err != nil {
87		t.Fatal(err)
88	}
89	if err := (ScaleStore{Path: link}).SaveOutputScale("DP-2", 2); err != nil {
90		t.Fatal(err)
91	}
92	if info, err := os.Lstat(link); err != nil || info.Mode()&os.ModeSymlink == 0 {
93		t.Fatal("symlink replaced", err)
94	}
95	if got, _ := os.ReadFile(target); string(got) != "border.width = 3\noutput.DP-2.scale = 2\n" {
96		t.Fatalf("%q", got)
97	}
98	// A dangling link is an error and stays in place.
99	if err := os.Remove(target); err != nil {
100		t.Fatal(err)
101	}
102	if err := (ScaleStore{Path: link}).SaveOutputScale("DP-2", 2); err == nil {
103		t.Fatal("dangling link saved")
104	}
105	if info, err := os.Lstat(link); err != nil || info.Mode()&os.ModeSymlink == 0 {
106		t.Fatal("dangling symlink replaced", err)
107	}
108}
109
110// Every clean scale is written in a form the parser reads back exactly.
111func TestFormatScaleRoundTrip(t *testing.T) {
112	for n := 120; n <= 480; n++ {
113		s := float64(n) / 120
114		got, err := parseScale(formatScale(s))
115		if err != nil || math.Round(got*120) != float64(n) {
116			t.Fatalf("%d/120: %q parsed as %v, %v", n, formatScale(s), got, err)
117		}
118	}
119}
120
121// Output names may hold spaces (EDID model names).
122func TestSaveOutputScaleSpacedName(t *testing.T) {
123	path := filepath.Join(t.TempDir(), "config")
124	if err := os.WriteFile(path, []byte("output.LG Electronics 27GR95UM.scale = 2\n"), 0o600); err != nil {
125		t.Fatal(err)
126	}
127	if err := (ScaleStore{Path: path}).SaveOutputScale("LG Electronics 27GR95UM", 1.5); err != nil {
128		t.Fatal(err)
129	}
130	if got, _ := os.ReadFile(path); string(got) != "output.LG Electronics 27GR95UM.scale = 1.5\n" {
131		t.Fatalf("%q", got)
132	}
133}

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 25

Tags 10

↑↓ moveenter openesc close