diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-15 17:57:25 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-15 17:57:25 +0200 |
| commit | a248608721e813d5e6d19fc79d2505a2a8c2716a (patch) | |
| tree | d3d5b2154f9aa94a8a30aeb42bcf5ff5a1bbcd5f /internal/notify/icons_test.go | |
| parent | 4eb086eeb44023aec23f5a48bd643b86024e3717 (diff) | |
| download | notifyd-a248608721e813d5e6d19fc79d2505a2a8c2716a.tar.gz notifyd-a248608721e813d5e6d19fc79d2505a2a8c2716a.zip | |
fix(notify): search freedesktop icon layout and rank by effective size
The hicolor fallback scanned <theme>/apps/<size>, but freedesktop themes
use <size>/apps, so hicolor never resolved. Both layouts are now
searched, and rasters are ranked by effective pixel size (N@2x is 2N)
with a deterministic name tie-break.
Diffstat (limited to 'internal/notify/icons_test.go')
| -rw-r--r-- | internal/notify/icons_test.go | 88 |
1 files changed, 88 insertions, 0 deletions
diff --git a/internal/notify/icons_test.go b/internal/notify/icons_test.go index 527b330..9a6824a 100644 --- a/internal/notify/icons_test.go +++ b/internal/notify/icons_test.go @@ -68,3 +68,91 @@ func TestResolveIconThemeName(t *testing.T) { t.Fatalf("missing name must be empty, got %q", got) } } + +// iconEnv builds a throwaway XDG tree, points qt6ct at theme, and returns the +// root. HOME, XDG_DATA_HOME and XDG_DATA_DIRS are restored after the test. +func iconEnv(t *testing.T, theme string) string { + t.Helper() + root := t.TempDir() + home := filepath.Join(root, "home") + if err := os.MkdirAll(filepath.Join(home, ".config", "qt6ct"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(home, ".config", "qt6ct", "qt6ct.conf"), []byte("icon_theme="+theme+"\n"), 0o644); err != nil { + t.Fatal(err) + } + t.Setenv("XDG_DATA_HOME", root) + t.Setenv("XDG_DATA_DIRS", "") + t.Setenv("HOME", home) + return root +} + +// putIcon creates a placeholder icon file, and its parents. +func putIcon(t *testing.T, path string) { + t.Helper() + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte("<svg/>"), 0o644); err != nil { + t.Fatal(err) + } +} + +// writeIndex writes a theme's index.theme with a single Inherits value. +func writeIndex(t *testing.T, dir, inherits string) { + t.Helper() + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "index.theme"), []byte("[Icon Theme]\nInherits="+inherits+"\n"), 0o644); err != nil { + t.Fatal(err) + } +} + +func TestResolveIconFreedesktopLayout(t *testing.T) { + root := iconEnv(t, "Flat") + putIcon(t, filepath.Join(root, "icons", "Flat", "scalable", "apps", "firefox.svg")) + putIcon(t, filepath.Join(root, "icons", "Flat", "48x48", "apps", "firefox.png")) + want := filepath.Join(root, "icons", "Flat", "scalable", "apps", "firefox.svg") + if got := ResolveIcon("firefox"); got != want { + t.Fatalf("freedesktop scalable: got %q want %q", got, want) + } + + root = iconEnv(t, "Grid") + putIcon(t, filepath.Join(root, "icons", "Grid", "48x48", "apps", "gimp.png")) + want = filepath.Join(root, "icons", "Grid", "48x48", "apps", "gimp.png") + if got := ResolveIcon("gimp"); got != want { + t.Fatalf("freedesktop raster: got %q want %q", got, want) + } +} + +func TestResolveIconSizePreference(t *testing.T) { + root := iconEnv(t, "Sized") + base := filepath.Join(root, "icons", "Sized") + putIcon(t, filepath.Join(base, "apps", "48", "app.png")) + putIcon(t, filepath.Join(base, "apps", "32@2x", "app.png")) + want := filepath.Join(base, "apps", "32@2x", "app.png") + if got := ResolveIcon("app"); got != want { + t.Fatalf("@2x beats plain 48 (64px vs 48px): got %q want %q", got, want) + } + + root = iconEnv(t, "Tied") + base = filepath.Join(root, "icons", "Tied") + putIcon(t, filepath.Join(base, "32x32@2x", "apps", "app.png")) + putIcon(t, filepath.Join(base, "64x64", "apps", "app.png")) + want = filepath.Join(base, "32x32@2x", "apps", "app.png") + if got := ResolveIcon("app"); got != want { + t.Fatalf("equal 64px must break ties deterministically: got %q want %q", got, want) + } +} + +func TestResolveIconInheritsCycle(t *testing.T) { + root := iconEnv(t, "LoopA") + writeIndex(t, filepath.Join(root, "icons", "LoopA"), "LoopB") + writeIndex(t, filepath.Join(root, "icons", "LoopB"), "LoopA") + putIcon(t, filepath.Join(root, "icons", "hicolor", "48x48", "apps", "app.png")) + want := filepath.Join(root, "icons", "hicolor", "48x48", "apps", "app.png") + if got := ResolveIcon("app"); got != want { + t.Fatalf("cycle must terminate and fall through to hicolor: got %q want %q", got, want) + } +} |
