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