// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package config import ( "os" "path/filepath" "reflect" "strings" "testing" ) func TestSpeechNumbersPersistWithOneDecimal(t *testing.T) { settings := DefaultSettings() rate, pitch, gain := 61.29999999999999, 4.499999999999998, 4.999999999999999 settings.DefaultVoice = Voice{Rate: &rate, AveragePitch: &pitch, Gain: &gain} settings.UppercaseVoice.AveragePitch = &pitch settings.HyperlinkVoice.Rate = &rate settings.SystemVoice.Gain = &gain settings.EchoVoice = settings.DefaultVoice path := filepath.Join(t.TempDir(), "settings.toml") store, err := NewStore(path) if err != nil { t.Fatal(err) } if err := store.Save(settings); err != nil { t.Fatal(err) } loaded, err := store.Load() if err != nil { t.Fatal(err) } for index, voice := range []Voice{loaded.DefaultVoice, loaded.UppercaseVoice, loaded.HyperlinkVoice, loaded.SystemVoice, loaded.EchoVoice} { for field, value := range []*float64{voice.Rate, voice.AveragePitch, voice.Gain} { if value != nil && *value != []float64{61.3, 4.5, 5}[field] { t.Fatalf("voice %d field %d = %g", index, field, *value) } } } if loaded.UppercaseVoice.Rate != nil || loaded.HyperlinkVoice.Gain != nil || loaded.SystemVoice.AveragePitch != nil { t.Fatal("rounding turned inheritance into explicit settings") } if *settings.DefaultVoice.AveragePitch != 4.499999999999998 { t.Fatal("saving mutated caller's voice") } contents, err := os.ReadFile(path) if err != nil || strings.Contains(string(contents), "999999") || !strings.Contains(string(contents), "average-pitch = 4.5") { t.Fatalf("TOML does not contain rounded decimal values: %v", err) } if err := store.SaveForApplication("Example Editor", settings); err != nil { t.Fatal(err) } contents, err = os.ReadFile(path) if err != nil { t.Fatal(err) } document, _, err := decodeDocument(contents) if err != nil { t.Fatal(err) } profile := document.Profiles[document.Applications["Example Editor"]] if profile.Speech.DefaultVoice != nil || profile.KeyEcho.EchoVoice != nil { t.Fatal("floating-point noise created application voice overrides") } appGain := 3.299999999999999 settings.DefaultVoice.Gain = &appGain if err := store.SaveForApplication("Example Editor", settings); err != nil { t.Fatal(err) } application, found, err := store.LoadForApplication("Example Editor") if err != nil || !found || *application.DefaultVoice.Gain != 3.3 { t.Fatalf("application speech was not rounded: %+v, %v", application.DefaultVoice, err) } } func TestVoiceTypesPersistAndInheritAcrossApplications(t *testing.T) { settings := DefaultSettings() rate, pitch, gain := 62.0, 8.0, 4.0 settings.DefaultVoice = Voice{Rate: &rate, AveragePitch: &pitch, Gain: &gain, Family: &VoiceFamily{Name: "Default person", Lang: "en"}} upper, link, system := 8.5, 3.0, 2.0 settings.UppercaseVoice.AveragePitch = &upper settings.HyperlinkVoice.Rate = &link settings.SystemVoice.Gain = &system settings.Reading.IndentationPresentationMode = "both" store, err := NewStore(filepath.Join(t.TempDir(), "settings.toml")) if err != nil { t.Fatal(err) } if err := store.Save(settings); err != nil { t.Fatal(err) } loaded, err := store.Load() if err != nil { t.Fatal(err) } for _, kind := range []string{"uppercase", "hyperlink", "system"} { if !reflect.DeepEqual(EffectiveVoice(loaded, kind), EffectiveVoice(settings, kind)) { t.Fatalf("%s did not persist", kind) } } if loaded.Reading.IndentationPresentationMode != "both" { t.Fatal("indentation mode lost") } appSettings := loaded appPitch := 5.0 appSettings.UppercaseVoice.AveragePitch = &appPitch if err := store.SaveForApplication("example-app", appSettings); err != nil { t.Fatal(err) } application, found, err := store.LoadForApplication("example-app") if err != nil || !found || *EffectiveVoice(application, "uppercase").AveragePitch != 5 { t.Fatalf("application voice = %+v, %v", application.UppercaseVoice, err) } global, err := store.Load() if err != nil || *EffectiveVoice(global, "uppercase").AveragePitch != 8.5 { t.Fatal("application voice changed global voice") } *store.Current().UppercaseVoice.AveragePitch = 0 if *store.Current().UppercaseVoice.AveragePitch != 8.5 { t.Fatal("voice snapshots alias") } appSettings.UppercaseVoice = Voice{} if err := store.SaveForApplication("example-app", appSettings); err != nil { t.Fatal(err) } application, found, err = store.LoadForApplication("example-app") if err != nil || !found || application.UppercaseVoice.AveragePitch != nil || *EffectiveVoice(application, "uppercase").AveragePitch != 9 { t.Fatalf("application automatic pitch reset = %+v, %v", application.UppercaseVoice, err) } } func TestAutomaticUppercasePitchAndExplicitOverride(t *testing.T) { settings := DefaultSettings() for _, tc := range []struct{ normal, upper float64 }{{3, 7}, {5, 7}, {7, 9}, {8, 9}, {9, 9}} { settings.DefaultVoice.AveragePitch = &tc.normal if got := *EffectiveVoice(settings, "uppercase").AveragePitch; got != tc.upper { t.Fatalf("normal %g: uppercase %g, want %g", tc.normal, got, tc.upper) } } chosen := 6.0 settings.UppercaseVoice.AveragePitch = &chosen if *EffectiveVoice(settings, "uppercase").AveragePitch != chosen { t.Fatal("explicit uppercase pitch ignored") } settings.Reading.IndentationPresentationMode = "invalid" if Validate(settings) == nil { t.Fatal("invalid indentation mode accepted") } }