package notify import ( "context" "os" "path/filepath" "runtime" "strings" "testing" ) // TestMain cuts the tests off from the developer's real session bus so they // observe the notify-send fallback instead of popping real notifications. func TestMain(m *testing.M) { os.Setenv("DBUS_SESSION_BUS_ADDRESS", "unix:path=/nonexistent/talk-test-bus") os.Exit(m.Run()) } // TestShowCallsNotifySend: on Linux, Show goes through notify-send; here a // fake in PATH that records its arguments (a real process, no mock // framework). Title and text arrive unchanged. func TestShowCallsNotifySend(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("the notify-send path exists only on Linux") } dir := t.TempDir() seen := filepath.Join(dir, "seen.txt") script := "#!/bin/sh\nprintf '%s\\n' \"$@\" > \"" + seen + "\"\n" if err := os.WriteFile(filepath.Join(dir, "notify-send"), []byte(script), 0o755); err != nil { t.Fatal(err) } t.Setenv("PATH", dir) if !Available() { t.Fatal("Available = false with fake in PATH") } if err := Show(context.Background(), "quiet hours: build done"); err != nil { t.Fatalf("Show: %v", err) } b, err := os.ReadFile(seen) if err != nil { t.Fatal(err) } args := strings.Split(strings.TrimSpace(string(b)), "\n") if args[len(args)-1] != "quiet hours: build done" || args[len(args)-2] != Title { t.Errorf("notify-send arguments = %q, want ... %q %q", args, Title, "quiet hours: build done") } } // TestShowWithoutTool: a missing tool is a clear error. func TestShowWithoutTool(t *testing.T) { t.Setenv("PATH", t.TempDir()) if Available() { t.Fatal("Available = true without tool") } if err := Show(context.Background(), "x"); err == nil { t.Error("Show without tool: no error") } if !strings.Contains(Describe(), "not found") { t.Errorf("Describe = %q", Describe()) } } func TestAppleString(t *testing.T) { if got := appleString(`say "hello" \ bye`); got != `"say \"hello\" \\ bye"` { t.Errorf("appleString = %s", got) } } // TestToastIdentity: under talk's own AppUserModelID the toast header names // talk already, so the scripts carry only the body; under the PowerShell // fallback identity a first line names talk. func TestToastIdentity(t *testing.T) { a := Announcement{Text: "build done", CancelLabel: "Cancel", CancelURI: "talk:cancel?id=1", Tag: "1"} own := cancelToastScript(a, "talk-1", AppID) if !strings.Contains(own, "CreateToastNotifier('"+AppID+"')") || strings.Contains(own, ""+Title+"") { t.Errorf("own identity script wrong:\n%s", own) } fallback := cancelToastScript(a, "talk-1", powerShellAppID) if !strings.Contains(fallback, "CreateToastNotifier('"+powerShellAppID+"')") || !strings.Contains(fallback, ""+Title+"") { t.Errorf("fallback script wrong:\n%s", fallback) } plain := toastScript("quiet", AppID) if strings.Contains(plain, ""+Title+"") || !strings.Contains(plain, "$msg") { t.Errorf("plain own-identity script wrong:\n%s", plain) } if !strings.Contains(toastScript("quiet", powerShellAppID), ""+Title+"") { t.Error("plain fallback script lacks the talk line") } } // TestUseIconOutsideWindows: UseIcon remembers the file and the freedesktop // notification carries it as app_icon; without one a theme icon is used. func TestUseIconOutsideWindows(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("registry path on Windows") } setIconPath("") if appIcon() != "audio-speakers" { t.Errorf("appIcon without file = %q", appIcon()) } if err := UseIcon("/x/talk.png"); err != nil { t.Fatal(err) } t.Cleanup(func() { setIconPath("") }) if appIcon() != "/x/talk.png" { t.Errorf("appIcon = %q", appIcon()) } if ownIdentity() { t.Error("ownIdentity true outside Windows") } } // TestSetup: the icon lands in dir and is used; with no exe the protocol // step is skipped (outside Windows it is a no-op anyway). func TestSetup(t *testing.T) { dir := t.TempDir() if err := Setup(dir, ""); err != nil { t.Fatal(err) } t.Cleanup(func() { setIconPath("") }) want := filepath.Join(dir, "talk.png") if _, err := os.Stat(want); err != nil { t.Fatalf("icon not installed: %v", err) } if runtime.GOOS != "windows" && appIcon() != want { t.Errorf("appIcon = %q, want %q", appIcon(), want) } if err := Setup("", ""); err == nil { t.Error("Setup without dir succeeded") } }