main f2d166b

rules_test.go RawHistoryFolder

1package config
2
3import (
4	"strconv"
5	"strings"
6	"testing"
7
8	"github.com/bnema/kvconf"
9)
10
11func TestRulesInFileOrder(t *testing.T) {
12	c, w := parseString(t, `
13workspace.chat.max-columns = 2
14bind.cmd+c = workspace chat
15rule.games.app-id = steam_app_.*
16rule.games.floating = on
17rule.games.workspace = 3
18rule.chat.app-id = org\.telegram\..*|discord
19rule.chat.workspace = chat
20rule.chat.monitor = DP-2
21rule.chat.width = 1
22rule.games.width = 4
23`)
24	if len(w) != 0 {
25		t.Fatal(w)
26	}
27	if len(c.Rules) != 2 || c.Rules[0].Name != "games" || c.Rules[1].Name != "chat" {
28		t.Fatalf("%+v", c.Rules)
29	}
30	g, ch := c.Rules[0], c.Rules[1]
31	if g.Floating == nil || !*g.Floating || g.Workspace != "3" || g.Cells != 4 || g.Monitor != "" {
32		t.Fatalf("%+v", g)
33	}
34	if ch.Floating != nil || ch.Workspace != "chat" || ch.Monitor != "DP-2" || ch.Cells != 1 {
35		t.Fatalf("%+v", ch)
36	}
37	if !g.AppID.MatchString("steam_app_1245620") || !ch.AppID.MatchString("discord") || !ch.AppID.MatchString("org.telegram.desktop") {
38		t.Fatal("app-id did not match")
39	}
40}
41
42func TestRuleAppIDIsAnchored(t *testing.T) {
43	c, _ := parseString(t, "rule.a.app-id = steam\nrule.b.app-id = steam|foot\n")
44	for _, tc := range []struct {
45		rule int
46		id   string
47		want bool
48	}{
49		{0, "steam", true}, {0, "steam_app_1", false}, {0, "xsteam", false}, {0, "", false},
50		// The alternation is anchored as a whole, not per branch.
51		{1, "foot", true}, {1, "footclient", false}, {1, "steamfoot", false},
52	} {
53		if got := c.Rules[tc.rule].AppID.MatchString(tc.id); got != tc.want {
54			t.Errorf("rule %d on %q = %v, want %v", tc.rule, tc.id, got, tc.want)
55		}
56	}
57}
58
59func TestRuleWarnings(t *testing.T) {
60	for _, tc := range []struct {
61		name, text, warn string
62		line, rules      int
63	}{
64		{"bad regex", "rule.a.app-id = (\n", "rule.a.app-id: invalid regular expression", 1, 0},
65		// Closing the anchoring group would match any app ID.
66		{"anchor breakout", "rule.a.app-id = steam)|(.*\n", "rule.a.app-id: invalid regular expression", 1, 0},
67		// Quoting the anchoring group's ")" must warn, not panic.
68		{"quoted anchor", "rule.a.app-id = \\Qsteam\n", "rule.a.app-id: invalid regular expression", 1, 0},
69		{"missing app-id", "rule.a.floating = on\n", "rule.a: no app-id, rule ignored", 1, 0},
70		{"unknown field", "rule.a.app-id = foo\nrule.a.title = x\n", "rule.a.title: unknown key", 2, 1},
71		{"bad floating", "rule.a.app-id = foo\nrule.a.floating = maybe\n", "rule.a.floating: must be on or off", 2, 0},
72		// An invalid duplicate keeps the earlier value and the rule.
73		{"bad duplicate floating", "rule.a.app-id = foo\nrule.a.floating = on\nrule.a.floating = maybe\n", "rule.a.floating: must be on or off", 3, 1},
74		{"bad duplicate app-id", "rule.a.app-id = foo\nrule.a.app-id = (\n", "rule.a.app-id: invalid regular expression", 2, 1},
75		{"bad width", "rule.a.app-id = foo\nrule.a.width = wide\n", "rule.a.width: must be a cell count from 1 to 16", 2, 0},
76		{"percent width", "rule.a.app-id = foo\nrule.a.width = 50%\n", "rule.a.width: must be a cell count", 2, 0},
77		{"fraction width", "rule.a.app-id = foo\nrule.a.width = 1/3\n", "rule.a.width: must be a cell count", 2, 0},
78		{"pixel width", "rule.a.app-id = foo\nrule.a.width = 800px\n", "rule.a.width: must be a cell count", 2, 0},
79		{"zero width", "rule.a.app-id = foo\nrule.a.width = 0\n", "rule.a.width: must be a cell count", 2, 0},
80		{"too wide", "rule.a.app-id = foo\nrule.a.width = 17\n", "rule.a.width: must be a cell count", 2, 0},
81		{"bad workspace number", "rule.a.app-id = foo\nrule.a.workspace = 0\n", "rule.a.workspace: workspace number", 2, 0},
82		{"bad workspace name", "rule.a.app-id = foo\nrule.a.workspace = a b\n", "rule.a.workspace: must be", 2, 0},
83		{"empty monitor", "rule.a.app-id = foo\nrule.a.monitor =\n", "rule.a.monitor: needs", 2, 0},
84		{"bad rule name", "rule.a b.app-id = foo\n", "rule name must be", 1, 0},
85		{"undeclared workspace", "rule.a.app-id = foo\nrule.a.workspace = web\n", "rule.a.workspace: no workspace.web.* declared", 2, 1},
86	} {
87		t.Run(tc.name, func(t *testing.T) {
88			c, w := parseString(t, tc.text)
89			if len(w) != 1 || w[0].Line != tc.line || !strings.Contains(w[0].Msg, tc.warn) {
90				t.Fatalf("warnings = %v, want %q at line %d", w, tc.warn, tc.line)
91			}
92			if len(c.Rules) != tc.rules {
93				t.Fatalf("rules = %+v, want %d", c.Rules, tc.rules)
94			}
95		})
96	}
97}
98
99func TestRuleWidthIsACellCount(t *testing.T) {
100	for _, v := range []int{1, 2, 16} {
101		c, w := parseString(t, "rule.a.app-id = foo\nrule.a.width = "+strconv.Itoa(v)+"\n")
102		if len(w) != 0 || len(c.Rules) != 1 || c.Rules[0].Cells != v {
103			t.Fatalf("%d: %v %+v", v, w, c.Rules)
104		}
105	}
106}
107
108func TestRuleReloadDiff(t *testing.T) {
109	_, a, _ := parseRaw(t, "rule.a.app-id = foo\nrule.b.app-id = bar\n")
110	_, b, _ := parseRaw(t, "rule.b.app-id = bar\nrule.a.app-id = foo\n")
111	_, c, _ := parseRaw(t, "rule.a.app-id = foo\nrule.b.app-id = bar\nrule.b.floating = on\n")
112	if d := kvconf.Changed(a, b); len(d) == 0 {
113		t.Fatal("reordering rules is not a change")
114	}
115	if d := kvconf.Changed(a, c); len(d) != 1 || d[0] != "rule.b.floating" {
116		t.Fatal(d)
117	}
118	if d := kvconf.Changed(a, a); len(d) != 0 {
119		t.Fatal(d)
120	}
121}
122
123func parseRaw(t *testing.T, s string) (cfg any, raw map[string]string, w []Warning) {
124	t.Helper()
125	c, raw, w, err := parseBytes([]byte(s))
126	if err != nil {
127		t.Fatal(err)
128	}
129	return c, raw, w
130}

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