package app import ( "context" "log/slog" "testing" "time" "git.stormux.org/storm/cthulhu/internal/atspi" ) func TestLiveRegionSpeaksInsertedChatWithoutEditableFocus(t *testing.T) { speech := &fakeSpeechQueue{spoken: make(chan string, 4)} adapter := &fakeAdapter{events: make(chan atspi.Event, 4)} ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { defer close(done) processEvents(ctx, slog.New(slog.DiscardHandler), speech, adapter, 0) }() defer func() { cancel(); <-done }() region := atspi.Snapshot{ BusName: ":1.7", Path: "/chat/status", Role: "status", LiveRegion: true, LivePoliteness: "polite", LiveAtomic: true, Text: "Username: Test message", } placeholder := region placeholder.Text = "\ufffc" adapter.events <- atspi.NewEventWithPayload(atspi.TextInsertedEvent, placeholder, "\ufffc") select { case got := <-speech.spoken: t.Fatalf("placeholder spoken as %q", got) case <-time.After(30 * time.Millisecond): } adapter.events <- atspi.NewEvent(atspi.ChildrenAddedEvent, region) select { case got := <-speech.spoken: if got != "Username: Test message" { t.Fatalf("speech = %q", got) } case <-time.After(time.Second): t.Fatal("live region was not spoken") } adapter.events <- atspi.NewEventWithPayload(atspi.TextInsertedEvent, region, "Username: Test message") select { case got := <-speech.spoken: t.Fatalf("duplicate live region speech %q", got) case <-time.After(30 * time.Millisecond): } region.Text = "Username: Next message" adapter.events <- atspi.NewEventWithPayload(atspi.TextInsertedEvent, region, "Next message") select { case got := <-speech.spoken: if got != "Username: Next message" { // Atomic regions speak their current full content, supplied by the // adapter, rather than only the insertion payload. t.Fatalf("atomic speech = %q", got) } case <-time.After(time.Second): t.Fatal("atomic live region insertion was not spoken") } } func TestLiveRegionOffDoesNotSpeak(t *testing.T) { speech := &fakeSpeechQueue{spoken: make(chan string, 2)} adapter := &fakeAdapter{events: make(chan atspi.Event, 2)} ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { defer close(done) processEvents(ctx, slog.New(slog.DiscardHandler), speech, adapter, 0) }() defer func() { cancel(); <-done }() region := atspi.Snapshot{BusName: ":1.7", Path: "/chat/status", Role: "status", LiveRegion: true, LivePoliteness: "off", Text: "Silence"} adapter.events <- atspi.NewEvent(atspi.ChildrenAddedEvent, region) select { case got := <-speech.spoken: t.Fatalf("unexpected speech %q", got) case <-time.After(30 * time.Millisecond): } }