// Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. package notify import ( "os" "path/filepath" "testing" "github.com/godbus/dbus/v5" ) // soundTree makes $XDG_DATA_HOME/sounds//stereo/ for each entry // and an index.theme per theme with the given Inherits, and points // XDG_DATA_HOME there with XDG_DATA_DIRS empty-but-set to another temp dir. func soundTree(t *testing.T, inherits map[string]string, files ...string) string { t.Helper() home := t.TempDir() t.Setenv("XDG_DATA_HOME", home) t.Setenv("XDG_DATA_DIRS", t.TempDir()) for theme, inh := range inherits { dir := filepath.Join(home, "sounds", theme) if err := os.MkdirAll(filepath.Join(dir, "stereo"), 0o755); err != nil { t.Fatal(err) } body := "[Sound Theme]\nName=" + theme + "\n" if inh != "" { body += "Inherits=" + inh + "\n" } if err := os.WriteFile(filepath.Join(dir, "index.theme"), []byte(body), 0o644); err != nil { t.Fatal(err) } } for _, f := range files { p := filepath.Join(home, "sounds", f) if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(p, []byte("x"), 0o644); err != nil { t.Fatal(err) } } return filepath.Join(home, "sounds") } func TestLookupSound(t *testing.T) { root := soundTree(t, map[string]string{"mine": "parent", "parent": "", "freedesktop": ""}, "mine/stereo/bell.oga", "mine/stereo/bell.wav", "parent/stereo/message-new-instant.ogg", "freedesktop/stereo/dialog-warning.oga", "freedesktop/stereo/message.oga", ) cases := []struct{ name, want string }{ {"bell", root + "/mine/stereo/bell.oga"}, // direct, .oga before .wav {"message-new-instant", root + "/parent/stereo/message-new-instant.ogg"}, // inherited {"dialog-warning", root + "/freedesktop/stereo/dialog-warning.oga"}, // freedesktop fallback {"message-new-email", root + "/freedesktop/stereo/message.oga"}, // name fallback {"nothing-here", ""}, } for _, c := range cases { if got := LookupSound(c.name, "mine"); got != c.want { t.Errorf("LookupSound(%q) = %q, want %q", c.name, got, c.want) } } } func TestLookupSoundInheritsCycle(t *testing.T) { soundTree(t, map[string]string{"a": "b", "b": "a"}) if got := LookupSound("bell", "a"); got != "" { t.Errorf("got %q, want empty", got) } } func TestSoundFromHints(t *testing.T) { root := soundTree(t, map[string]string{"freedesktop": ""}, "freedesktop/stereo/bell.oga") file := filepath.Join(t.TempDir(), "ding.wav") if err := os.WriteFile(file, []byte("x"), 0o644); err != nil { t.Fatal(err) } v := dbus.MakeVariant cases := []struct { name string hints map[string]dbus.Variant sound string silent bool }{ {"none", map[string]dbus.Variant{}, "", false}, {"name", map[string]dbus.Variant{"sound-name": v("bell")}, root + "/freedesktop/stereo/bell.oga", false}, {"file wins", map[string]dbus.Variant{"sound-name": v("bell"), "sound-file": v(file)}, file, false}, {"relative file rejected", map[string]dbus.Variant{"sound-file": v("ding.wav")}, "", false}, {"missing file rejected", map[string]dbus.Variant{"sound-file": v(file + ".gone")}, "", false}, {"bad file falls to name", map[string]dbus.Variant{"sound-file": v("x"), "sound-name": v("bell")}, root + "/freedesktop/stereo/bell.oga", false}, {"suppress", map[string]dbus.Variant{"suppress-sound": v(true), "sound-name": v("bell")}, root + "/freedesktop/stereo/bell.oga", true}, {"wrong types", map[string]dbus.Variant{"suppress-sound": v("yes"), "sound-name": v(3)}, "", false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { sound, silent := SoundFromHints(c.hints, "freedesktop") if sound != c.sound || silent != c.silent { t.Errorf("got (%q, %v), want (%q, %v)", sound, silent, c.sound, c.silent) } }) } }