Files
cthulhu/internal/app/libreoffice_headers_test.go

120 lines
4.8 KiB
Go

// 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"
"git.stormux.org/storm/cthulhu/internal/braille"
"git.stormux.org/storm/cthulhu/internal/config"
"git.stormux.org/storm/cthulhu/internal/input"
"git.stormux.org/storm/cthulhu/internal/presentation"
)
func TestLibreOfficeHeaderCommandsRespectOwnershipAndGates(t *testing.T) {
event := calcTestEvent(atspi.FocusEvent, "/cell", "10", "B3")
event.Source.TableRowIndex, event.Source.TableColumnIndex = 2, 1
event.Source.LibreOffice.Document = atspi.NewObjectID(event.Source.BusName, "/document")
event.Source.LibreOffice.SheetName = "Example"
state := eventLoopState{}
state.presentationOptions = presentation.DefaultOptions()
state.current = event.Source
state.owner.follow(event)
speech := &fakeSpeechQueue{spoken: make(chan string, 8)}
gate := &focusGate{}
frames := make(chan braille.Frame, 8)
handler := eventReviewHandler{state: &state, speech: speech, focusGate: gate,
calcHeaders: atspi.New(), syncForeground: func() {}, brailleOutput: calcFrameQueue{frames}}
for _, test := range []struct {
command input.Command
want string
}{
{input.SetColumnHeadersRow, "Row 3 set as column headers."},
{input.SetRowHeadersColumn, "Column B set as row headers."},
{input.ClearColumnHeadersRow, "Column headers cleared."},
{input.ClearRowHeadersColumn, "Row headers cleared."},
} {
handler.configureCalcHeader(test.command)
if got := <-speech.spoken; got != test.want {
t.Fatalf("header command = %q", got)
}
<-frames
}
// Clearing immediately refreshes the Braille base frame as well as the
// copied Where Am I snapshot, without waiting for another cell event.
state.current.TableColumnHeaders, state.current.TableRowHeaders = "Total", "Monday"
handler.configureCalcHeader(input.ClearColumnHeadersRow)
<-speech.spoken
if frame := <-frames; frame.Text != "Monday 10 B3" {
t.Fatalf("cleared frame = %+v", frame)
}
if state.current.TableColumnHeaders != "" || state.brailleSnapshot.TableColumnHeaders != "" {
t.Fatal("clear retained column heading")
}
gate.secure.Store(true)
handler.configureCalcHeader(input.SetColumnHeadersRow)
gate.secure.Store(false)
gate.suspended.Store(true)
handler.configureCalcHeader(input.SetColumnHeadersRow)
gate.suspended.Store(false)
if len(speech.spoken) != 0 || len(frames) != 0 {
t.Fatal("header command bypassed lock/suspension")
}
state.owner.focus = atspi.NewObjectID(event.Source.BusName, "/other")
handler.configureCalcHeader(input.SetColumnHeadersRow)
if got := <-speech.spoken; got != "Not in a Calc cell." {
t.Fatal(got)
}
handler.syncForeground = func() { state.current = atspi.Snapshot{} }
handler.configureCalcHeader(input.SetColumnHeadersRow)
if got := <-speech.spoken; got != "Not in a Calc cell." {
t.Fatal(got)
}
}
func TestLibreOfficeHeaderSpeechHistoryAndBraille(t *testing.T) {
event := calcTestEvent(atspi.FocusEvent, "/cell", "10", "B3")
event.Source.TableRowIndex, event.Source.TableColumnIndex = 2, 1
event.Source.TableRowHeaders, event.Source.TableColumnHeaders = "Monday", "Total"
state := eventLoopState{}
state.lastTable, state.lastTableCell = calcSheet(event.Source), event.Source.ID()
state.lastTableRow, state.lastTableColumn = 2, 0
filtered := state.calcHeaderSpeechEvent(event)
if filtered.Source.TableRowHeaders != "" || filtered.Source.TableColumnHeaders != "Total" {
t.Fatalf("horizontal: %+v", filtered.Source)
}
state.lastTableRow, state.lastTableColumn = 1, 1
filtered = state.calcHeaderSpeechEvent(event)
if filtered.Source.TableRowHeaders != "Monday" || filtered.Source.TableColumnHeaders != "" {
t.Fatalf("vertical: %+v", filtered.Source)
}
// The accepted snapshot retains both for Where Am I and Braille.
if got := presentation.FocusText(event.Source); got != "Monday row header, Total column header, 10, B3" {
t.Fatal(got)
}
if frame := brailleFocusFrame(event.Source); frame.Text != "Monday Total 10 B3" {
t.Fatalf("frame = %+v", frame)
}
reading := config.DefaultSettings().Reading
reading.OnlySpeakDisplayedText = true
options := presentation.DefaultOptions()
options.Reading = &reading
if got := presentation.FocusTextWithOptions(event.Source, options); got != "10" {
t.Fatal(got)
}
state.current = event.Source
state.clearObjectFocus(nil, nil, nil)
filtered = state.calcHeaderSpeechEvent(event)
if filtered.Source.TableRowHeaders == "" || filtered.Source.TableColumnHeaders == "" {
t.Fatal("focus return suppressed headers")
}
state.lastTable = atspi.NewObjectID(event.Source.BusName, "/other-sheet")
filtered = state.calcHeaderSpeechEvent(event)
if filtered.Source.TableRowHeaders == "" || filtered.Source.TableColumnHeaders == "" {
t.Fatal("sheet return suppressed headers")
}
}