// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package atspi import ( "errors" "fmt" "testing" ) func TestDocumentMissingObjectRetiresCachedTarget(t *testing.T) { adapter := New() adapter.tree, adapter.documentTree, adapter.documentTextTree = testTree(t), testTree(t), testTree(t) object := testID("/second") failure := fmt.Errorf("get document caret: Method %q with signature %q on interface %q could not be processed as object %s does not exist\n", "Get", "ss", "org.freedesktop.DBus.Properties", object.Path) err := adapter.document_object_error(object, failure) if !errors.Is(err, ErrDeadObject) || !errors.Is(err, failure) { t.Fatalf("missing-object error lost classification or details: %v", err) } for _, tree := range []*AccessibleTree{adapter.tree, adapter.documentTree, adapter.documentTextTree} { if _, err := tree.Reference(object); !errors.Is(err, ErrObjectNotFound) { t.Fatalf("removed target remains cached: %v", err) } if _, err := tree.Reference(testID("/grandchild")); !errors.Is(err, ErrObjectNotFound) { t.Fatalf("removed descendant remains cached: %v", err) } if _, err := tree.Reference(testID("/first")); err != nil { t.Fatalf("live sibling was retired: %v", err) } root, _ := tree.Root() if _, _, err := tree.FirstChild(root); !errors.Is(err, ErrIncompleteTree) { t.Fatalf("parent child order was not marked for refresh: %v", err) } } } func TestDocumentCaretFailuresDoNotRetireLiveTargets(t *testing.T) { for _, message := range []string{ "browser refused document caret", "get document caret: timeout", "get document caret: unknown property CaretOffset", "could not be processed as object /other does not exist", "could not be processed as object /second_child does not exist", } { t.Run(message, func(t *testing.T) { adapter := New() adapter.documentTree = testTree(t) failure := errors.New(message) if err := adapter.document_object_error(testID("/second"), failure); err != failure { t.Fatalf("ordinary failure reclassified: %v", err) } if _, err := adapter.documentTree.Reference(testID("/second")); err != nil { t.Fatalf("unconfirmed removal retired live target: %v", err) } }) } } func TestDocumentEmptyTextRefreshesMetadataAndParentWithoutRetiringTargets(t *testing.T) { adapter := New() tree := testTree(t) adapter.documentTree = tree object := testID("/second") target, _ := tree.Reference(object) sibling, _ := tree.Reference(testID("/first")) if err := adapter.document_text_became_empty(object); !errors.Is(err, ErrStaleText) { t.Fatalf("unexpected empty-text classification: %v", err) } if current, err := tree.Reference(object); err != nil || current != target { t.Fatalf("empty text retired an unconfirmed target: %v", err) } if _, err := tree.Resolve(target); !errors.Is(err, ErrStaleSnapshot) { t.Fatalf("text metadata did not require refresh: %v", err) } if _, err := tree.Resolve(sibling); err != nil { t.Fatalf("live sibling was invalidated: %v", err) } root, _ := tree.Root() if _, _, err := tree.FirstChild(root); !errors.Is(err, ErrIncompleteTree) { t.Fatalf("parent's children did not require refresh: %v", err) } } func TestDocumentMissingDescendantRefreshesAncestorChildOrder(t *testing.T) { adapter := New() adapter.tree, adapter.documentTree, adapter.documentTextTree = testTree(t), testTree(t), testTree(t) object := testID("/grandchild") failure := fmt.Errorf("get document caret: could not be processed as object %s does not exist", object.Path) if err := adapter.document_object_error(object, failure); !errors.Is(err, ErrDeadObject) { t.Fatal(err) } for _, tree := range []*AccessibleTree{adapter.tree, adapter.documentTree, adapter.documentTextTree} { for _, id := range []ObjectID{testID("/second"), testID("/root")} { ref, err := tree.Reference(id) if err != nil { t.Fatal(err) } if _, _, err := tree.FirstChild(ref); !errors.Is(err, ErrIncompleteTree) { t.Fatalf("ancestor %s retained obsolete child order: %v", id.Path, err) } } // Refresh ancestry without declaring unrelated siblings dead or dirty. sibling, err := tree.Reference(testID("/first")) if err != nil { t.Fatal(err) } if _, err := tree.Resolve(sibling); err != nil { t.Fatal(err) } if _, _, err := tree.FirstChild(sibling); err != nil { t.Fatal(err) } if _, err := tree.Reference(object); !errors.Is(err, ErrObjectNotFound) { t.Fatalf("missing object survived: %v", err) } } }