// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "context" "log/slog" "testing" "time" "git.stormux.org/storm/cthulhu/internal/atspi" ) func TestUnfocusedPasswordCaretDuringSettingsCreation(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() adapter := &fakeAdapter{events: make(chan atspi.Event, 8)} speech := &fakeSpeechQueue{spoken: make(chan string, 8)} done := make(chan struct{}) go func() { defer close(done) processEvents(ctx, slog.New(slog.DiscardHandler), speech, adapter, 0) }() defer func() { cancel(); <-done }() focus := func(path, name, role string) { source := atspi.NewSnapshot(path, name, role, "", "") source.BusName = ":1.42" adapter.events <- atspi.NewEvent(atspi.FocusEvent, source) } focus("/restart", "Restart interface", "button") password := atspi.NewSnapshot("/proxy-password", "", "password text", "", "") password.BusName = ":1.42" adapter.events <- atspi.NewEvent(atspi.CaretEvent, password) focus("/interface", "Interface", "combo box") // A real focus event still announces the password field. The following // button is an event-loop barrier, so stray caret speech cannot pass later. focus("/proxy-password", "Proxy password", "password text") focus("/close", "Close", "button") for _, want := range []string{"Restart interface, button", "Interface, combo box", "Proxy password, password text", "Close, button"} { select { case got := <-speech.spoken: if got != want { t.Fatalf("speech = %q, want %q", got, want) } case <-ctx.Done(): t.Fatalf("missing speech %q", want) } } }