Files
2026-10-28 22:06:04 -04:00

210 lines
7.4 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package preferences
import (
"math"
"reflect"
"testing"
"git.stormux.org/storm/cthulhu/internal/config"
)
func TestSpeechValuesMergeRoundsNumbersAndPreservesInheritance(t *testing.T) {
settings := config.DefaultSettings()
values := FromSettings(settings)
rate, pitch, gain := 61.29999999999999, 4.499999999999998, 4.999999999999999
values.Rate, values.Pitch, values.Volume = &rate, &pitch, &gain
values.UppercaseVoice.AveragePitch = &pitch
values.HyperlinkVoice.Rate = &rate
values.SystemVoice.Gain = &gain
values.EchoVoice = config.Voice{Rate: &rate, AveragePitch: &pitch, Gain: &gain}
merged, err := values.Merge(settings)
if err != nil {
t.Fatal(err)
}
if *merged.DefaultVoice.Rate != 61.3 || *merged.DefaultVoice.AveragePitch != 4.5 || *merged.DefaultVoice.Gain != 5 ||
*merged.UppercaseVoice.AveragePitch != 4.5 || *merged.HyperlinkVoice.Rate != 61.3 || *merged.SystemVoice.Gain != 5 ||
*merged.EchoVoice.Rate != 61.3 || *merged.EchoVoice.AveragePitch != 4.5 || *merged.EchoVoice.Gain != 5 {
t.Fatal("speech values retained floating-point noise")
}
if merged.UppercaseVoice.Rate != nil || merged.HyperlinkVoice.Gain != nil || merged.SystemVoice.AveragePitch != nil {
t.Fatal("rounding turned inheritance into explicit settings")
}
if pitch != 4.499999999999998 {
t.Fatal("merging mutated caller's values")
}
for _, invalid := range []float64{9.01, -0.01, math.NaN(), math.Inf(1)} {
values.Pitch = &invalid
if _, err := values.Merge(settings); err == nil {
t.Fatalf("rounding hid invalid pitch %g", invalid)
}
}
}
func TestSoundValuesDefaultAndMerge(t *testing.T) {
settings := config.DefaultSettings()
values := FromSettings(settings)
if !values.EnableSound || !values.PlaySoundForRole || !values.PlaySoundForState ||
!values.PlaySoundForPositionInSet ||
values.ProgressBar.OutputMode != "beep" || values.SpatializeObjectSounds {
t.Fatalf("default sound checkboxes = %+v", values)
}
values.EnableSound = false
values.EnableBraille = true
values.EnableContractedBraille = true
values.BrailleContractionTable = "en-us-g2.ctb"
values.SoundVolume = 0.75
values.SoundSink = "pulse"
values.SoundTheme = "none"
values.PlaySoundForRole = false
values.PlaySoundForState = false
values.PlaySoundForPositionInSet = false
values.SpatializeObjectSounds = true
values.ProgressBar.OutputMode = "off"
values.ProgressBar.ReportBackground = true
values.RoleSoundPresentation = "speech_only"
merged, err := values.Merge(settings)
if err != nil {
t.Fatal(err)
}
if got := FromSettings(merged); !reflect.DeepEqual(got, values) {
t.Fatalf("sound values did not round trip:\n got %+v\nwant %+v", got, values)
}
}
func TestReadingValuesRoundTrip(t *testing.T) {
settings := config.DefaultSettings()
values := FromSettings(settings)
if values.Reading.OnlySpeakDisplayedText || !values.Reading.SpeakBlankLines ||
!values.SpeakDescription || values.Reading.ReadFullRowInSpreadSheet ||
!values.EnableSpeech || !values.Reading.EnablePauseBreaks ||
values.Reading.SpeakNumbersAsDigits || values.Reading.SpeakMultiCaseStringsAsWords ||
values.Reading.EnableIndentation || values.Reading.PhoneticCharacterHints {
t.Fatalf("default reading values = %+v", values)
}
values.EnableSpeech = false
values.SayAllStyle = "sentence"
values.SpeechVerbosityLevel = 0
values.Reading.OnlySpeakDisplayedText = true
values.Reading.EnablePauseBreaks = false
values.Reading.SpeakNumbersAsDigits = true
values.Reading.SpeakMultiCaseStringsAsWords = true
values.Reading.EnableIndentation = true
values.Reading.PhoneticCharacterHints = true
values.Reading.SpeakBlankLines = false
values.Reading.EnableMnemonicSpeaking = true
values.Reading.EnablePositionSpeaking = true
values.Reading.EnableTutorialMessages = true
values.Reading.MessagesAreDetailed = false
values.Reading.UseColorNames = false
values.Reading.PresentTooltips = true
values.Reading.EnableMouseReview = true
values.Reading.SpeakMisspelledIndicator = false
values.Reading.SpeakContextBlockquote = false
values.Reading.SpeakContextList = false
values.Reading.SpeakContextTable = false
values.Reading.SpeakContextPanel = false
values.Reading.SpeakContextLandmark = false
values.Reading.SpeakContextNonLandmarkForm = false
values.Reading.SayAllContextBlockquote = false
values.Reading.SayAllContextList = false
values.Reading.SayAllContextTable = false
values.Reading.SayAllContextPanel = false
values.Reading.SayAllContextLandmark = false
values.Reading.SayAllContextNonLandmarkForm = false
values.Reading.ReadFullRowInGUITable = false
values.Reading.ReadFullRowInDocumentTable = false
values.Reading.ReadFullRowInSpreadSheet = true
values.SpeakDescription = false
merged, err := values.Merge(settings)
if err != nil {
t.Fatal(err)
}
if got := FromSettings(merged); !reflect.DeepEqual(got, values) {
t.Fatalf("reading values did not round trip:\n got %+v\nwant %+v", got, values)
}
}
func TestSpeechValuesRoundTrip(t *testing.T) {
settings := config.DefaultSettings()
settings.SpeechHistoryLimit = 1000
family := &config.VoiceFamily{Name: "Generic Voice", Lang: "en", Dialect: "US", Variant: "male"}
settings.SpeechOutputModule = "espeak-ng"
settings.DefaultVoice.Family = family
values := FromSettings(settings)
if values.SpeechOutputModule != "espeak-ng" || values.SpeechVoiceFamily != family || values.SpeechHistoryLimit != 1000 {
t.Fatalf("speech values = %+v", values)
}
merged, err := values.Merge(settings)
if err != nil {
t.Fatal(err)
}
if merged.SpeechOutputModule != "espeak-ng" || merged.DefaultVoice.Family != family || merged.SpeechHistoryLimit != 1000 {
t.Fatalf("merged speech values = %+v", merged)
}
}
func TestKeyboardValuesMergeAndOwnership(t *testing.T) {
settings := config.DefaultSettings()
values := FromSettings(settings)
values.KeyboardLayout = 2
values.CthulhuModifierKeys = []string{"Caps_Lock", "Insert"}
merged, err := values.Merge(settings)
if err != nil {
t.Fatal(err)
}
if merged.KeyboardLayout != 2 || !reflect.DeepEqual(merged.CthulhuModifierKeys, values.CthulhuModifierKeys) {
t.Fatalf("keyboard settings were not merged: %+v", merged)
}
values.CthulhuModifierKeys[0] = "KP_Insert"
if merged.CthulhuModifierKeys[0] != "Caps_Lock" {
t.Fatal("merged modifiers alias the UI values")
}
values.CthulhuModifierKeys = []string{}
merged, err = values.Merge(settings)
if err != nil || len(merged.CthulhuModifierKeys) != 0 {
t.Fatalf("cannot disable all modifiers: %v", err)
}
}
func TestCapitalCheckboxesRoundTrip(t *testing.T) {
for mask := 0; mask < 8; mask++ {
settings := config.DefaultSettings()
values := FromSettings(settings)
values.RaisePitchForCapitals = mask&1 != 0
values.BeepForCapitals = mask&2 != 0
values.SayCapForCapitals = mask&4 != 0
merged, err := values.Merge(settings)
if err != nil {
t.Fatal(err)
}
if got := FromSettings(merged); !reflect.DeepEqual(got, values) {
t.Fatalf("mask %d did not round trip", mask)
}
}
}
func TestStructuralFocusValuesRoundTrip(t *testing.T) {
settings := config.DefaultSettings()
values := FromSettings(settings)
if values.StructuralNavigationTriggersFocusMode {
t.Fatal("automatic structural focus defaults on")
}
values.StructuralNavigationTriggersFocusMode = true
merged, err := values.Merge(settings)
if err != nil {
t.Fatal(err)
}
if !FromSettings(merged).StructuralNavigationTriggersFocusMode {
t.Fatal("preference lost during Apply")
}
}