mesh/merge-gate pass: builds dunst → g14, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer delivery to the same trunk took over its walk
Review of hq issue 360: rofi without -no-custom hands typed text to dunst as if chosen.
148 lines
6.0 KiB
Go
148 lines
6.0 KiB
Go
package main
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// dunst's shape (novox/hq ADR 0208): it claims node-notifier serving send and history, owns its
|
|
// dunstrc and the drop-in directory other modules' rules go in, and starts nothing — D-Bus starts it
|
|
// on the first notification; the mesh only tells a running one to read its file again. It draws only once a notification arrives, through the bus's
|
|
// activation, so it requires no display of its own.
|
|
|
|
func TestItClaimsTheNotifierSeatServingSendAndHistory(t *testing.T) {
|
|
m := readManifest(t)
|
|
if m.Module != "dunst" || m.Seats != nil {
|
|
t.Fatalf("module %q declares seats %v", m.Module, m.Seats)
|
|
}
|
|
if !reflect.DeepEqual(m.Claims, []claim{{Name: "node-notifier", Scope: "node", Serves: []string{"send", "history"}}}) {
|
|
t.Fatalf("claims: %+v", m.Claims)
|
|
}
|
|
// rofi, the context menu the dunstrc calls by name, is the rofi module's package: two modules on one
|
|
// machine may not declare the same package, and the rofi module holds node-launcher on every
|
|
// machine dunst serves.
|
|
if present, absent := m.packages(); !reflect.DeepEqual(present, []string{"dunst", "libnotify"}) || absent != nil {
|
|
t.Fatalf("packages: %v, absent %v", present, absent)
|
|
}
|
|
}
|
|
|
|
func TestItOwnsItsFileAndTheDropInDirectory(t *testing.T) {
|
|
m := readManifest(t)
|
|
m.sameAsSource(t, "configuration", "files/dunstrc")
|
|
if p := m.resource(t, "configuration")["path"]; p != "${machine:account-home}/.config/dunst/dunstrc" {
|
|
t.Fatalf("path: %v", p)
|
|
}
|
|
if d := m.resource(t, "dropins"); d["type"] != "directory" || d["path"] != "${machine:account-home}/.config/dunst/dunstrc.d" {
|
|
t.Fatalf("drop-ins: %v", d)
|
|
}
|
|
for _, r := range m.Resources {
|
|
if p, _ := r["path"].(string); strings.Contains(p, "dunstrc.d/") {
|
|
t.Fatalf("a rule of another module's: %v", r)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTheFileIsTheDecidedOneAndCallsRofi(t *testing.T) {
|
|
m := readManifest(t)
|
|
c := m.resource(t, "configuration")["content"].(string)
|
|
for _, want := range []string{"origin = top-right", "transparency = 0", "corner_radius = 0", "font = Inter ${setting:font-size}",
|
|
"width = ${setting:width}", "dmenu = rofi -dmenu -no-custom -p dunst"} {
|
|
if !strings.Contains(c, " "+want+"\n") {
|
|
t.Errorf("lacks %q", want)
|
|
}
|
|
}
|
|
// Not the node-launcher's `dmenu` in ~/.local/bin: dunst's service manager has no such directory on
|
|
// its PATH, so that menu never opened.
|
|
for _, never := range []string{"/usr/bin/dmenu", "dmenu = dmenu", "Hack", "icon_path", "/home/"} {
|
|
if strings.Contains(c, never) {
|
|
t.Errorf("names %q", never)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A tap chooses (the operator's touchpads have no middle button): the left click is dunst's do_action
|
|
// and then close_current, so a notification with one action runs it, one with several opens the menu
|
|
// of them (dunst holds it open until the menu answers, and an Escape leaves it shown), and one without
|
|
// actions closes. Every other click is as it was.
|
|
func TestATapChoosesTheNotificationsAction(t *testing.T) {
|
|
m := readManifest(t)
|
|
c := m.resource(t, "configuration")["content"].(string)
|
|
for _, want := range []string{"mouse_left_click = do_action, close_current",
|
|
"mouse_middle_click = do_action, close_current", "mouse_right_click = close_all"} {
|
|
if !strings.Contains(c, " "+want+"\n") {
|
|
t.Errorf("lacks %q", want)
|
|
}
|
|
}
|
|
if strings.Count(c, "mouse_left_click") != 1 {
|
|
t.Errorf("sets mouse_left_click more than once")
|
|
}
|
|
}
|
|
|
|
// The key that chooses from the keyboard is a line i3 places from this contribution (novox/hq ADR
|
|
// 0212): a binding only, never a start, so the bus stays the only thing that starts dunst.
|
|
func TestTheMenuKeyIsABindingI3Places(t *testing.T) {
|
|
m := readManifest(t)
|
|
m.i3LinesAreSource(t, "files/i3/50-dunst.conf")
|
|
lines := m.i3Lines()
|
|
if !strings.Contains(lines, "bindsym $mod+n exec --no-startup-id dunstctl context\n") {
|
|
t.Fatalf("no key for the context menu: %s", lines)
|
|
}
|
|
for _, l := range strings.Split(lines, "\n") {
|
|
if l = strings.TrimSpace(l); l != "" && !strings.HasPrefix(l, "#") && !strings.HasPrefix(l, "bindsym ") {
|
|
t.Errorf("a line that is not a key: %q", l)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The font size and the width are preferences with today's values as their defaults (novox/hq ADR
|
|
// 0262): a machine with a denser screen sets its own, and every other machine's file is unchanged.
|
|
func TestTheFontSizeAndWidthAreSettingsWithTodaysDefaults(t *testing.T) {
|
|
m := readManifest(t)
|
|
want := map[string]float64{"font-size": 10, "width": 250}
|
|
if len(m.Settings) != len(want) {
|
|
t.Fatalf("settings: %+v", m.Settings)
|
|
}
|
|
for key, value := range want {
|
|
s, ok := m.Settings[key]
|
|
if !ok || s.Kind != "preference" || s.Default != value || strings.TrimSpace(s.Why) == "" {
|
|
t.Errorf("%s: %+v, want a preference defaulting to %v with why", key, s, value)
|
|
}
|
|
}
|
|
// Filled with the defaults, the file is the one every machine had before it took settings.
|
|
c := m.resource(t, "configuration")["content"].(string)
|
|
c = strings.ReplaceAll(c, "${setting:font-size}", "10")
|
|
c = strings.ReplaceAll(c, "${setting:width}", "250")
|
|
for _, want := range []string{"font = Inter 10", "width = 250"} {
|
|
if !strings.Contains(c, " "+want+"\n") {
|
|
t.Errorf("filled with the defaults, lacks %q", want)
|
|
}
|
|
}
|
|
if strings.Contains(c, "${setting:") {
|
|
t.Errorf("asks for a setting it gives no default: %s", c)
|
|
}
|
|
}
|
|
|
|
func TestNothingStartsItButTheBus(t *testing.T) {
|
|
m := readManifest(t)
|
|
if m.Shell != nil {
|
|
t.Fatalf("a session start: %+v", m.Shell)
|
|
}
|
|
for _, r := range m.Resources {
|
|
// The one unit it names is the package's own dunst.service, without a state: its lifecycle stays
|
|
// the bus's, and the mesh only reloads it when it is running and its file changed (ADR 0117).
|
|
if r["type"] == "process" || (r["type"] == "service" && (r["state"] != nil || r["boot"] != nil)) {
|
|
t.Fatalf("a unit: %v", r)
|
|
}
|
|
if p, _ := r["path"].(string); strings.Contains(p, "autostart") || strings.Contains(p, "i3/config.d") {
|
|
t.Fatalf("a start: %v", r)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTheToolsAgreeWithTheManifest(t *testing.T) {
|
|
m := readManifest(t)
|
|
checkTheToolsAgree(t, m)
|
|
checkNoSecretsOrInstallationNames(t)
|
|
}
|