Files
cthulhu/internal/atspi/echo_focus_test.go

107 lines
3.4 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package atspi
import "testing"
func TestEchoEditFieldRequiresEditableMetadata(t *testing.T) {
for _, tc := range []struct {
name string
snapshot Snapshot
want bool
}{
{"entry", Snapshot{Role: "text", Editable: true}, true},
{"web contenteditable", Snapshot{Role: "section", Editable: true}, true},
{"editable combo", Snapshot{Role: "combo box", Editable: true}, true},
{"unknown text", Snapshot{Role: "text"}, false},
{"read only", Snapshot{Role: "text", Editable: true, ReadOnly: true}, false},
{"password", Snapshot{Role: "password text", Editable: true}, false},
{"terminal", Snapshot{Role: "terminal", Editable: true}, false},
{"defunct", Snapshot{Role: "text", Editable: true, Defunct: true}, false},
} {
t.Run(tc.name, func(t *testing.T) {
if got := IsEchoEditField(tc.snapshot); got != tc.want {
t.Fatalf("editable=%v want=%v", got, tc.want)
}
})
}
}
func TestEchoFocusLifetime(t *testing.T) {
adapter := New()
source := Snapshot{BusName: ":1.5", Path: "/edit", Role: "text", Editable: true, Focused: true, StatesKnown: true}
event := Event{Type: FocusedStateEvent, Detail1: 1, Source: source, Window: NewObjectID(source.BusName, "/window"), OriginPath: "/child"}
if adapter.EditFieldFocused() {
t.Fatal("unknown focus allowed")
}
gain := func() {
adapter.observeEchoFocus(event)
if !adapter.EditFieldFocused() {
t.Fatal("focused editor rejected")
}
}
gain()
// Background updates and losses do not replace the current editor.
adapter.retireEchoFocus(FocusedStateEvent, 0, NewObjectID(":1.6", "/edit"))
other := event
other.Source.Path = "/other"
other.ForegroundMismatch = true
adapter.observeEchoFocus(other)
if !adapter.EditFieldFocused() {
t.Fatal("unrelated event retired editor")
}
for _, tc := range []struct {
kind string
detail int
id ObjectID
}{
{FocusedStateEvent, 0, source.ID()}, {FocusedStateEvent, 0, NewObjectID(source.BusName, "/child")},
{DefunctStateEvent, 1, source.ID()}, {WindowDestroyedEvent, 0, event.Window}, {WindowDeactivatedEvent, 0, event.Window},
{FocusedStateEvent, 1, NewObjectID(source.BusName, "/button")},
} {
gain()
adapter.retireEchoFocus(tc.kind, tc.detail, tc.id)
if adapter.EditFieldFocused() {
t.Fatalf("stale focus after %s", tc.kind)
}
}
gain()
readonly := event
readonly.Type = "object:state-changed:read-only"
readonly.Source.ReadOnly = true
adapter.observeEchoFocus(readonly)
if adapter.EditFieldFocused() {
t.Fatal("read-only change retained echo")
}
gain()
adapter.foreground.Store(&ForegroundState{Epoch: 1})
if adapter.EditFieldFocused() {
t.Fatal("foreground change retained echo")
}
adapter.observeEchoFocus(event)
if adapter.EditFieldFocused() {
t.Fatal("old event restored echo")
}
adapter.foreground.Store(nil)
gain()
unknownWindow := event
unknownWindow.Type = WindowActivatedEvent
unknownWindow.Window = NewObjectID(":1.6", "/window")
unknownWindow.Source = Snapshot{BusName: ":1.6", Path: "/window", Role: "frame"}
adapter.observeEchoFocus(unknownWindow)
if adapter.EditFieldFocused() {
t.Fatal("unknown window retained previous editor without X11 metadata")
}
gain()
adapter.SetSecureInputActive(true)
if adapter.EditFieldFocused() {
t.Fatal("lock retained echo")
}
adapter.SetSecureInputActive(false)
if adapter.EditFieldFocused() {
t.Fatal("unlock restored stale echo")
}
}