package evidence import ( "context" "sync" "testing" "time" ) // TestPollLoopEmitsOnTitleChangeWithinSameWindow is the regression for the bug // where the X11 sensor only re-read the window on _NET_ACTIVE_WINDOW changes and // so missed a browser tab switch — same window class, new title — crediting all // the time to the stale title. The poll loop must emit when only the title // changes. func TestPollLoopEmitsOnTitleChangeWithinSameWindow(t *testing.T) { // Same window (class) throughout; the title changes mid-stream, like a tab // switch. The repeated identical reads must NOT re-emit; the title change must. titles := []string{"Keel - Brave", "Keel - Brave", "Consume - Brave", "Consume - Brave"} var i int read := func() WindowSnapshot { idx := i if idx >= len(titles) { idx = len(titles) - 1 } i++ return WindowSnapshot{Class: "Brave-browser", Title: titles[idx], Health: EvidenceHealth{Available: true}} } var mu sync.Mutex var got []WindowSnapshot onChange := func(s WindowSnapshot) { mu.Lock() got = append(got, s) mu.Unlock() } ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { pollLoop(ctx, time.Millisecond, read, onChange); close(done) }() deadline := time.Now().Add(2 * time.Second) for { mu.Lock() n := len(got) mu.Unlock() if n >= 2 { break } if time.Now().After(deadline) { cancel() <-done t.Fatalf("expected >=2 emits (one per distinct title), got %d", n) } time.Sleep(time.Millisecond) } cancel() <-done mu.Lock() defer mu.Unlock() if got[0].Title != "Keel - Brave" { t.Fatalf("first emit title = %q, want %q", got[0].Title, "Keel - Brave") } if got[1].Title != "Consume - Brave" { t.Fatalf("second emit title = %q, want %q (a title-only change must emit)", got[1].Title, "Consume - Brave") } } // TestPollLoopDedupesIdenticalObservations confirms an unchanged observation is // not re-emitted, so redundant samples never churn the consumer. func TestPollLoopDedupesIdenticalObservations(t *testing.T) { read := func() WindowSnapshot { return WindowSnapshot{Class: "code", Title: "main.go", Health: EvidenceHealth{Available: true}} } var mu sync.Mutex var n int onChange := func(WindowSnapshot) { mu.Lock() n++ mu.Unlock() } ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { pollLoop(ctx, time.Millisecond, read, onChange); close(done) }() time.Sleep(50 * time.Millisecond) cancel() <-done mu.Lock() defer mu.Unlock() if n != 1 { t.Fatalf("identical observations must emit once, got %d emits", n) } }