// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package presentation import ( "testing" "git.stormux.org/storm/cthulhu/internal/atspi" "git.stormux.org/storm/cthulhu/internal/config" ) func TestReadingPreferencesChangeControlOutput(t *testing.T) { reading := config.DefaultSettings().Reading options := DefaultOptions() options.Reading = &reading snapshot := atspi.Snapshot{Name: "Save", Role: "button", Description: "Save document", Mnemonic: "s", PositionInSet: 2, SetSize: 3} reading.EnableMnemonicSpeaking, reading.EnablePositionSpeaking, reading.EnableTutorialMessages = true, true, true text := FocusTextWithOptions(snapshot, options) want := "Save, button, Save document, 2 of 3, s, Press Space to activate." if text != want { t.Fatalf("enabled extras = %q, want %q", text, want) } reading.EnableMnemonicSpeaking, reading.EnablePositionSpeaking, reading.EnableTutorialMessages = false, false, false if got := FocusTextWithOptions(snapshot, options); got != "Save, button, Save document" { t.Fatalf("disabled extras: %q", got) } reading.EnablePauseBreaks = false if got := FocusTextWithOptions(snapshot, options); got != "Save button Save document" { t.Fatalf("pause breaks survived: %q", got) } reading.OnlySpeakDisplayedText = true if got := FocusTextWithOptions(snapshot, options); got != "Save" { t.Fatalf("displayed text: %q", got) } if got := EventTextWithOptions(atspi.NewEvent("object:state-changed:checked", atspi.Snapshot{Role: "check box", Checked: true}), options); got != "" { t.Fatalf("state speech survived displayed-only: %q", got) } reading.OnlySpeakDisplayedText = false line := atspi.DocumentLine{Contents: []atspi.DocumentContent{{Text: "word", Snapshot: atspi.Snapshot{Role: "text", Misspelled: true}}}} if got := DocumentLineTextWithOptions(line, options); got != "word misspelled" { t.Fatalf("misspelling: %q", got) } reading.SpeakMisspelledIndicator = false if got := DocumentLineTextWithOptions(line, options); got != "word" { t.Fatalf("disabled misspelling: %q", got) } } func TestDocumentAndSpreadsheetFullRowsUseIndependentFlags(t *testing.T) { reading := config.DefaultSettings().Reading options := DefaultOptions() options.Reading = &reading cell := atspi.Snapshot{Role: "table cell", Name: "Cell one", TableRowText: "Cell one, Cell two"} if got := FocusTextWithOptions(cell, options); got != cell.TableRowText { t.Fatalf("document row = %q", got) } reading.ReadFullRowInDocumentTable = false if got := FocusTextWithOptions(cell, options); got != cell.Name { t.Fatalf("document cell = %q", got) } cell.Spreadsheet = true reading.ReadFullRowInDocumentTable = true if got := FocusTextWithOptions(cell, options); got != cell.Name { t.Fatalf("document flag affected spreadsheet: %q", got) } reading.ReadFullRowInSpreadSheet = true if got := FocusTextWithOptions(cell, options); got != cell.TableRowText { t.Fatalf("spreadsheet row = %q", got) } } func TestDocumentLineReadsEachTableRowOnce(t *testing.T) { line := atspi.DocumentLine{} for _, name := range []string{"One", "Two"} { line.Contents = append(line.Contents, atspi.DocumentContent{Text: name, Snapshot: atspi.Snapshot{ BusName: ":1.42", Path: "/" + name, Role: "table cell", TablePath: "/table", TableRowText: "One, Two", }}) } if got := DocumentLineText(line); got != "One, Two" { t.Fatalf("repeated row: %q", got) } } func TestEachNavigationContextHasItsOwnPreference(t *testing.T) { for _, tc := range []struct { snapshot atspi.Snapshot kind string disable func(*config.ReadingSettings) }{ {atspi.Snapshot{Role: "block quote"}, "blockquote", func(r *config.ReadingSettings) { r.SpeakContextBlockquote = false }}, {atspi.Snapshot{Role: "list"}, "list", func(r *config.ReadingSettings) { r.SpeakContextList = false }}, {atspi.Snapshot{Role: "table"}, "table", func(r *config.ReadingSettings) { r.SpeakContextTable = false }}, {atspi.Snapshot{Role: "panel"}, "panel", func(r *config.ReadingSettings) { r.SpeakContextPanel = false }}, {atspi.Snapshot{Role: "section", XMLRoles: "navigation"}, "landmark", func(r *config.ReadingSettings) { r.SpeakContextLandmark = false }}, {atspi.Snapshot{Role: "form"}, "form", func(r *config.ReadingSettings) { r.SpeakContextNonLandmarkForm = false }}, } { reading := config.DefaultSettings().Reading if ContextType(tc.snapshot) != tc.kind || !ContextEnabled(tc.kind, reading) { t.Fatalf("missing context: %+v", tc) } tc.disable(&reading) if ContextEnabled(tc.kind, reading) { t.Fatalf("disabled context: %s", tc.kind) } } if ContextType(atspi.Snapshot{Role: "table", LayoutTable: true}) != "" { t.Fatal("layout table announced") } }