Files
cthulhu/internal/atspi/libreoffice_headers_test.go

162 lines
5.7 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package atspi
import (
"fmt"
"sync"
"testing"
)
func headerTestSnapshot() (Snapshot, ObjectID) {
return Snapshot{BusName: ":1.42", Path: "/cell", TablePath: "/sheet", Role: "table cell",
ApplicationProcess: "soffice.bin", TablePositionKnown: true, TableRowIndex: 2, TableColumnIndex: 3,
LibreOffice: &LibreOfficeContext{Cell: true, InSpreadsheet: true, Document: NewObjectID(":1.42", "/document"), SheetName: "Example"}}, NewObjectID(":1.42", "/window")
}
func TestLibreOfficeHeadersScopeLifetimeAndIndependentAxes(t *testing.T) {
adapter := New()
snapshot, window := headerTestSnapshot()
key, _ := libreOfficeHeaderIdentity(snapshot, window)
if err := adapter.ConfigureLibreOfficeHeader(snapshot, window, true, false); err != nil {
t.Fatal(err)
}
if err := adapter.ConfigureLibreOfficeHeader(snapshot, window, false, false); err != nil {
t.Fatal(err)
}
choice := adapter.libreOfficeHeaders.get(key)
if !choice.rowSet || choice.row != 2 || !choice.columnSet || choice.column != 3 {
t.Fatalf("choice = %+v", choice)
}
snapshot.TablePath = "/recreated-sheet-table"
recreated, _ := libreOfficeHeaderIdentity(snapshot, window)
if adapter.libreOfficeHeaders.get(recreated) != choice {
t.Fatal("sheet switch lost the document choice")
}
for _, other := range []libreOfficeHeaderKey{
{NewObjectID(":1.42", "/other-window"), key.document, key.sheet},
{key.window, NewObjectID(":1.42", "/other-document"), key.sheet},
{key.window, key.document, "Other"},
{NewObjectID(":1.43", "/window"), NewObjectID(":1.43", "/document"), "Example"},
} {
if got := adapter.libreOfficeHeaders.get(other); got.rowSet || got.columnSet {
t.Fatalf("headers crossed identity: %+v", other)
}
}
if err := adapter.ConfigureLibreOfficeHeader(snapshot, window, true, true); err != nil {
t.Fatal(err)
}
if got := adapter.libreOfficeHeaders.get(key); got.rowSet || !got.columnSet {
t.Fatalf("clearing one axis changed the other: %+v", got)
}
// Locking retires output/native focus, but document choices survive.
adapter.SetSecureInputActive(true)
if err := adapter.ConfigureLibreOfficeHeader(snapshot, window, true, false); err == nil {
t.Fatal("configuration accepted while locked")
}
adapter.SetSecureInputActive(false)
if !adapter.libreOfficeHeaders.get(key).columnSet {
t.Fatal("lock discarded the document choice")
}
adapter.libreOfficeHeaders.forget(NewObjectID(":1.42", "/other-window"))
if !adapter.libreOfficeHeaders.get(key).columnSet {
t.Fatal("unrelated destruction discarded choice")
}
for _, id := range []ObjectID{key.document, key.window} {
if err := adapter.ConfigureLibreOfficeHeader(snapshot, window, false, false); err != nil {
t.Fatal(err)
}
adapter.libreOfficeHeaders.forget(id)
if got := adapter.libreOfficeHeaders.get(key); got.rowSet || got.columnSet {
t.Fatalf("destruction retained choice: %+v", id)
}
}
_ = adapter.ConfigureLibreOfficeHeader(snapshot, window, false, false)
adapter.libreOfficeHeaders.reset()
if len(adapter.libreOfficeHeaders.choices) != 0 {
t.Fatal("shutdown retained choices")
}
_ = adapter.ConfigureLibreOfficeHeader(snapshot, window, true, false)
adapter.libreOfficeHeaders.forget(NewObjectID(":1.42", "/other-window"))
if !adapter.libreOfficeHeaders.get(key).rowSet {
t.Fatal("unrelated tab changes retired choice")
}
adapter.libreOfficeHeaders.forget(window)
if adapter.libreOfficeHeaders.get(key).rowSet {
t.Fatal("sheet topology change retained choice")
}
}
func TestLibreOfficeHeadersRejectInvalidContextAndBoundChoices(t *testing.T) {
for _, invalid := range []string{"process", "editor", "defunct", "position", "negative", "document", "sheet", "bus", "window"} {
snapshot, window := headerTestSnapshot()
switch invalid {
case "process":
snapshot.ApplicationProcess = "chromium"
case "editor":
snapshot.Role = "paragraph"
case "defunct":
snapshot.Defunct = true
case "position":
snapshot.TablePositionKnown = false
case "negative":
snapshot.TableColumnIndex = -1
case "document":
snapshot.LibreOffice.Document = ObjectID{}
case "sheet":
snapshot.LibreOffice.SheetName = ""
case "bus":
window.BusName = ":1.43"
case "window":
window = ObjectID{}
}
adapter := New()
if err := adapter.ConfigureLibreOfficeHeader(snapshot, window, true, false); err == nil {
t.Fatalf("accepted invalid %s context", invalid)
}
}
adapter := New()
snapshot, window := headerTestSnapshot()
for index := 0; index < 65; index++ {
snapshot.LibreOffice.SheetName = fmt.Sprintf("Sheet%d", index)
err := adapter.ConfigureLibreOfficeHeader(snapshot, window, true, false)
if (err != nil) != (index == 64) {
t.Fatalf("bounded choice %d: %v", index, err)
}
}
snapshot.LibreOffice.SheetName = "Sheet0"
if err := adapter.ConfigureLibreOfficeHeader(snapshot, window, true, true); err != nil {
t.Fatal(err)
}
snapshot.LibreOffice.SheetName = "Sheet64"
if err := adapter.ConfigureLibreOfficeHeader(snapshot, window, true, false); err != nil {
t.Fatal("clear did not free capacity:", err)
}
}
func TestLibreOfficeHeadersConcurrentCaptureAndCommands(t *testing.T) {
adapter := New()
snapshot, window := headerTestSnapshot()
key, _ := libreOfficeHeaderIdentity(snapshot, window)
var workers sync.WaitGroup
for index := 0; index < 3; index++ {
workers.Add(1)
go func(index int) {
defer workers.Done()
for count := 0; count < 100; count++ {
switch index {
case 0:
_ = adapter.ConfigureLibreOfficeHeader(snapshot, window, true, count%2 == 0)
case 1:
_ = adapter.libreOfficeHeaders.get(key)
case 2:
adapter.libreOfficeHeaders.forget(window)
}
}
}(index)
}
workers.Wait()
}