feat(notify): Desktop-Benachrichtigung für den Ruhezeit-Toast
quiet_mode = "toast" war bisher nur eine Protokollzeile. Jetzt zeigt der Daemon eine Desktop-Benachrichtigung — über die Bordmittel des Systems, exec-basiert wie osbuiltin, ohne Bibliothek und ohne CGO: notify-send (Linux), osascript display notification (macOS), PowerShell Toast (Windows; Text base64, kein Quoting-Problem). Best effort: fehlt das Werkzeug, bleibt es bei der Protokollzeile plus Start-Warnung; `talk doctor` zeigt den Weg unter „Toast". Der Aufruf läuft asynchron, ein hängender Notification-Daemon blockiert die Queue nie. Queue: OnToast-Hook, gefeuert bei Toast-Verwurf beim Eintritt und im Worker (nie unter dem Mutex). Tests: notify-Paket mit fake notify-send im PATH, Prozessgrenze: Ruhezeit + toast → fake notify-send bekommt Titel und Text, der Provider keinen Treffer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
a9ca32bed9
commit
9e2aa506a6
7 files changed
+279
-6
No files matched your search
@@ -0,0 +1,61 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestShowRuftNotifySend: Auf Linux geht Show über notify-send — hier ein
|
||||
// Fake im PATH, der seine Argumente protokolliert (echter Prozess, kein
|
||||
// Mock-Framework). Titel und Text kommen unverändert an.
|
||||
func TestShowRuftNotifySend(t *testing.T) {
|
||||
if runtime.GOOS != "linux" {
|
||||
t.Skip("notify-send-Weg gibt es nur auf 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 mit Fake im PATH")
|
||||
}
|
||||
if err := Show(context.Background(), "Ruhezeit: Build fertig"); 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] != "Ruhezeit: Build fertig" || args[len(args)-2] != Title {
|
||||
t.Errorf("notify-send-Argumente = %q, want … %q %q", args, Title, "Ruhezeit: Build fertig")
|
||||
}
|
||||
}
|
||||
|
||||
// TestShowOhneWerkzeug: fehlt das Werkzeug, ist das ein klarer Fehler.
|
||||
func TestShowOhneWerkzeug(t *testing.T) {
|
||||
t.Setenv("PATH", t.TempDir())
|
||||
if Available() {
|
||||
t.Fatal("Available = true ohne Werkzeug")
|
||||
}
|
||||
if err := Show(context.Background(), "x"); err == nil {
|
||||
t.Error("Show ohne Werkzeug: kein Fehler")
|
||||
}
|
||||
if !strings.Contains(Describe(), "nicht gefunden") {
|
||||
t.Errorf("Describe = %q", Describe())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppleString(t *testing.T) {
|
||||
if got := appleString(`sag "hallo" \ tschüss`); got != `"sag \"hallo\" \\ tschüss"` {
|
||||
t.Errorf("appleString = %s", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user