feat(04-01): integrate notifications and focus mode into daemon
- Notifier + FocusTimer fields in Daemon, initialized in NewDaemon - processHookEvent notifies on Working -> Needs Input only (D-01) - Focus mode suppresses notifications when active (D-05) - FocusArgs in protocol, "focus" handler in daemon - CLI "vmux focus <minutes>" command - Tests for all notification transitions and focus handler
This commit is contained in:
116
daemon_test.go
116
daemon_test.go
@@ -432,6 +432,122 @@ func TestDaemonLabelOverSocket(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// spyNotifier records calls to Notify for test assertions.
|
||||
type spyNotifier struct {
|
||||
calls []struct{ title, body string }
|
||||
}
|
||||
|
||||
func (s *spyNotifier) Notify(title, body string) error {
|
||||
s.calls = append(s.calls, struct{ title, body string }{title, body})
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestNotification_WorkingToNeedsInput(t *testing.T) {
|
||||
d := newTestDaemon(t)
|
||||
spy := &spyNotifier{}
|
||||
d.notifier = spy
|
||||
|
||||
// Seed session as Working
|
||||
d.registry.UpdateFromHook("sess-1", "Working", "", "/tmp/proj")
|
||||
|
||||
// Transition to Needs Input
|
||||
d.processHookEvent(HookEvent{
|
||||
SessionID: "sess-1",
|
||||
HookEventName: "Stop",
|
||||
Cwd: "/tmp/proj",
|
||||
})
|
||||
|
||||
if len(spy.calls) != 1 {
|
||||
t.Fatalf("notify calls = %d, want 1", len(spy.calls))
|
||||
}
|
||||
if spy.calls[0].title != "vmux: proj" {
|
||||
t.Errorf("title = %q, want %q", spy.calls[0].title, "vmux: proj")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotification_IdleToNeedsInput(t *testing.T) {
|
||||
d := newTestDaemon(t)
|
||||
spy := &spyNotifier{}
|
||||
d.notifier = spy
|
||||
|
||||
// Seed session as Idle (not Working)
|
||||
d.registry.UpdateFromHook("sess-1", "Idle", "", "/tmp/proj")
|
||||
|
||||
// Transition to Needs Input from Idle
|
||||
d.processHookEvent(HookEvent{
|
||||
SessionID: "sess-1",
|
||||
HookEventName: "Stop",
|
||||
Cwd: "/tmp/proj",
|
||||
})
|
||||
|
||||
if len(spy.calls) != 0 {
|
||||
t.Errorf("notify calls = %d, want 0 (Idle -> Needs Input should not notify)", len(spy.calls))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotification_FocusActive(t *testing.T) {
|
||||
d := newTestDaemon(t)
|
||||
spy := &spyNotifier{}
|
||||
d.notifier = spy
|
||||
d.focus.Set(30 * time.Minute)
|
||||
|
||||
// Seed session as Working
|
||||
d.registry.UpdateFromHook("sess-1", "Working", "", "/tmp/proj")
|
||||
|
||||
// Transition to Needs Input (but focus active)
|
||||
d.processHookEvent(HookEvent{
|
||||
SessionID: "sess-1",
|
||||
HookEventName: "Stop",
|
||||
Cwd: "/tmp/proj",
|
||||
})
|
||||
|
||||
if len(spy.calls) != 0 {
|
||||
t.Errorf("notify calls = %d, want 0 (focus active should suppress)", len(spy.calls))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotification_FocusExpired(t *testing.T) {
|
||||
d := newTestDaemon(t)
|
||||
spy := &spyNotifier{}
|
||||
d.notifier = spy
|
||||
d.focus.Set(0) // expired immediately
|
||||
|
||||
// Seed session as Working
|
||||
d.registry.UpdateFromHook("sess-1", "Working", "", "/tmp/proj")
|
||||
|
||||
// Transition to Needs Input (focus expired)
|
||||
d.processHookEvent(HookEvent{
|
||||
SessionID: "sess-1",
|
||||
HookEventName: "Stop",
|
||||
Cwd: "/tmp/proj",
|
||||
})
|
||||
|
||||
if len(spy.calls) != 1 {
|
||||
t.Fatalf("notify calls = %d, want 1 (focus expired should notify)", len(spy.calls))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFocusHandler(t *testing.T) {
|
||||
d := newTestDaemon(t)
|
||||
|
||||
if err := d.Start(); err != nil {
|
||||
t.Fatalf("start: %v", err)
|
||||
}
|
||||
defer d.Stop()
|
||||
|
||||
args, _ := json.Marshal(FocusArgs{Minutes: 30})
|
||||
resp := sendRequest(t, d.sockPath, Request{Action: "focus", Args: args})
|
||||
if !resp.OK {
|
||||
t.Fatalf("focus resp.OK = false, error = %q", resp.Error)
|
||||
}
|
||||
if !d.focus.IsActive() {
|
||||
t.Error("focus should be active after handler")
|
||||
}
|
||||
if resp.FocusRemaining <= 0 {
|
||||
t.Errorf("FocusRemaining = %v, want > 0", resp.FocusRemaining)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonUnknownAction(t *testing.T) {
|
||||
d := newTestDaemon(t)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user