// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "testing" "git.stormux.org/storm/cthulhu/internal/atspi" ) func ownerEvent(eventType, bus, window, focus string) atspi.Event { event := atspi.NewEvent(eventType, atspi.Snapshot{BusName: bus, Path: focus}) event.Window = atspi.NewObjectID(bus, window) return event } func TestWindowOwnerKeepsWindowAndFocusIndependent(t *testing.T) { var owner windowOwner activation := ownerEvent(atspi.WindowActivatedEvent, ":1.40", "/window", "/button") if owner.activate(activation) { t.Fatal("first activation was treated as duplicate") } if owner.window != activation.Window || owner.focus != activation.Source.ID() || owner.applicationBus != ":1.40" { t.Fatalf("owner = %#v", owner) } focus := ownerEvent(atspi.FocusedStateEvent, ":1.40", "/window", "/entry") if !owner.follow(focus) || owner.window != activation.Window || owner.focus != focus.Source.ID() { t.Fatalf("focus did not remain in active window: %#v", owner) } if !owner.activate(activation) || owner.focus != focus.Source.ID() { t.Fatal("duplicate activation replaced descendant focus") } } func TestFocusBeforeActivationStillAdmitsFirstWindowActivation(t *testing.T) { var owner windowOwner focus := ownerEvent(atspi.FocusedStateEvent, ":1.40", "/window", "/entry") focus.Source.Focused = true if !owner.follow(focus) || owner.activated { t.Fatalf("focus-first ownership = %#v", owner) } activation := ownerEvent(atspi.WindowActivatedEvent, ":1.40", "/window", "/window") if owner.activate(activation) || owner.focus != focus.Source.ID() { t.Fatalf("first activation lost focus or was skipped: %#v", owner) } if !owner.activate(activation) { t.Fatal("repeated activation was not suppressed") } } func TestWindowOwnerRejectsBackgroundFocusAndUnrelatedLifecycle(t *testing.T) { var owner windowOwner owner.activate(ownerEvent(atspi.WindowActivatedEvent, ":1.40", "/window", "/button")) if owner.follow(ownerEvent(atspi.FocusedStateEvent, ":1.41", "/other", "/entry")) { t.Fatal("background focus changed ownership") } if owner.end(ownerEvent(atspi.WindowDestroyedEvent, ":1.41", "/other", "/other"), false) { t.Fatal("unrelated destruction cleared ownership") } if !owner.window.Valid() { t.Fatal("unrelated lifecycle cleared active window") } } func TestWindowOwnerHandlesTransientDeactivationAndDestruction(t *testing.T) { var owner windowOwner activation := ownerEvent(atspi.WindowActivatedEvent, ":1.40", "/window", "/button") owner.activate(activation) deactivation := ownerEvent(atspi.WindowDeactivatedEvent, ":1.40", "/window", "/window") deactivation.Source.StatesKnown = true deactivation.Source.Active = true if owner.end(deactivation, false) || !owner.window.Valid() { t.Fatal("active key-grab deactivation cleared ownership") } deactivation.Source.StatesKnown = false deactivation.Source.Active = false if owner.end(deactivation, true) || !owner.window.Valid() { t.Fatal("consumed-command deactivation cleared ownership") } destruction := ownerEvent(atspi.WindowDestroyedEvent, ":1.40", "/window", "/window") if !owner.end(destruction, true) || owner.window.Valid() || owner.focus.Valid() || owner.applicationBus != "" { t.Fatalf("destruction did not clear ownership: %#v", owner) } } func TestWindowOwnerClearsDefunctFocusWithoutDroppingWindow(t *testing.T) { var owner windowOwner activation := ownerEvent(atspi.WindowActivatedEvent, ":1.40", "/window", "/button") owner.activate(activation) defunct := atspi.NewEvent(atspi.DefunctStateEvent, activation.Source) defunct.Detail1 = 1 windowEnded, focusEnded := owner.defunct(defunct) if windowEnded || !focusEnded || !owner.window.Valid() || owner.focus.Valid() { t.Fatalf("defunct focus result = windowEnded:%v focusEnded:%v owner:%#v", windowEnded, focusEnded, owner) } windowDefunct := atspi.NewEvent(atspi.DefunctStateEvent, atspi.Snapshot{BusName: ":1.40", Path: "/window"}) windowDefunct.Detail1 = 1 windowEnded, focusEnded = owner.defunct(windowDefunct) if !windowEnded || !focusEnded || owner.window.Valid() || owner.applicationBus != "" { t.Fatalf("defunct window result = windowEnded:%v focusEnded:%v owner:%#v", windowEnded, focusEnded, owner) } }