Files

156 lines
6.1 KiB
Go

// Fenrir console screen reader.
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
package config
import (
"os"
"path/filepath"
"reflect"
"strings"
"testing"
)
func TestVersionOneMigrationPreservesValuesAndOnlyWritesOnSave(t *testing.T) {
original := []byte(`version = 1
[clipboard]
sync = true
sync_display = ":42"
number_of_clipboards = 7
export_path = "/tmp/fixture-clipboard"
[sound]
enabled = false
volume = 0.3
[speech]
enabled = false
rate = 45
module = "fixture"
socket = "/tmp/fixture.sock"
[keyboard]
layout = "laptop"
devices = ["/dev/input/event-fixture"]
character_echo = false
word_echo = true
echo_on_capslock = true
[reading]
automatic = false
cursor_echo = false
`)
path := filepath.Join(t.TempDir(), "settings.toml")
if err := os.WriteFile(path, original, 0600); err != nil {
t.Fatal(err)
}
doc, err := Open_document(path, Defaults())
if err != nil {
t.Fatal(err)
}
wanted := Defaults()
wanted.Presentation.Sound = Sound{Enabled: false, Volume: 0.3, Driver: "pulse", Theme: "default", Device: "default"}
wanted.Presentation.Speech = Speech{Enabled: false, Rate: 45, Volume: 100, Driver: "speech-dispatcher", Module: "fixture"}
wanted.Keyboard = Keyboard{MultiPressTimeoutMS: 200, Layout: "laptop", Devices: []string{"/dev/input/event-fixture"}, WordEcho: true, EchoOnCapsLock: true}
wanted.Reading = Reading{Emoticons: true, RepeatedSymbolsMin: 4, Spellcheck: true, SpellcheckSound: true, Attributes: Defaults().Reading.Attributes, AttributeFormat: Defaults().Reading.AttributeFormat, Indentation: "none", RaisePitchForCapitals: true, Mode: "cursor", PunctuationProfile: "default", PunctuationLevel: "some"}
wanted.Clipboard = Clipboard{Sync: true, SyncDisplay: ":42", NumberOfClipboards: 7, ExportPath: "/tmp/fixture-clipboard"}
if !reflect.DeepEqual(doc.Settings, wanted) {
t.Fatal(doc.Settings, wanted)
}
data, err := os.ReadFile(path)
if err != nil || string(data) != string(original) {
t.Fatal("opening rewrote settings", err)
}
if err := doc.Save(doc.Settings); err != nil {
t.Fatal(err)
}
data, err = os.ReadFile(path)
if err != nil || !strings.Contains(string(data), "version = 3") || strings.Contains(string(data), "\n[sound]") || strings.Contains(string(data), "socket =") {
t.Fatal(string(data), err)
}
got, err := Open_document(path, Defaults())
wantedOrder := []string{"clipboard", "presentation", "presentation.sound", "presentation.speech", "presentation.progress", "presentation.time_and_date", "keyboard", "reading", "internal_ui", "review", "screen", "general", "scripts", "remote", "prompt"}
if err != nil || !reflect.DeepEqual(got.Settings, wanted) || !reflect.DeepEqual(got.Order, wantedOrder) {
t.Fatal(got, err)
}
}
func TestRejectMixedSchemasAndUnknownDriverEvenWhenSpeechDisabled(t *testing.T) {
for _, input := range []string{
"version=1\n[presentation.speech]\nrate=40\n",
"version=2\n[speech]\nrate=40\n",
"version=2\n[presentation.speech]\ndriver='invented'\nenabled=false\n",
"version=1\n[speech]\ndriver='invented'\n",
"version=2\n[presentation.speech]\ndrivers=['speech-dispatcher']\n",
"version=0\n", "version=4\n",
"version=2\n[presentation.sound]\nvolume=nan\n",
"version=2\n[presentation.progress]\nenabled=true\n",
} {
if _, err := Decode([]byte(input)); err == nil {
t.Fatal("invalid schema accepted", input)
}
}
}
func TestNestedDottedAndInlineSectionsShareOrder(t *testing.T) {
for _, input := range []string{
"version=2\n[reading]\nautomatic=false\n[presentation.sound]\nvolume=0.2\n[presentation.speech]\nrate=40\n",
"version=2\nreading.automatic=false\npresentation.sound.volume=0.2\npresentation.speech.rate=40\n",
"version=2\nreading={automatic=false}\npresentation.sound={volume=0.2}\npresentation.speech={rate=40}\n",
"version=2\nreading={automatic=false}\npresentation={sound={volume=0.2}, speech={rate=40}}\n",
"version=2\n[reading]\nautomatic=false\n[presentation]\nsound={volume=0.2}\nspeech={rate=40}\n",
} {
path := filepath.Join(t.TempDir(), "settings.toml")
if err := os.WriteFile(path, []byte(input), 0600); err != nil {
t.Fatal(err)
}
doc, err := Open_document(path, Defaults())
if err != nil {
t.Fatal(err)
}
want := []string{"reading", "presentation", "presentation.sound", "presentation.speech"}
if !reflect.DeepEqual(doc.Order, want) {
t.Fatal(doc.Order)
}
if err := doc.Save(doc.Settings); err != nil {
t.Fatal(err)
}
saved, err := Open_document(path, Defaults())
if err == nil {
saved.Settings.Keyboard.Devices = doc.Settings.Keyboard.Devices
}
if err != nil || !reflect.DeepEqual(saved.Settings, doc.Settings) {
t.Fatal(saved, err)
}
}
}
func TestVersionTwoMigrationDiscardsSocketAndCurrentSchemaRejectsIt(t *testing.T) {
old := []byte("version=2\n[presentation.speech]\nsocket='/tmp/obsolete.sock'\nrate=45\nmodule='fixture'\n[presentation.sound]\nvolume=0.3\n")
settings, err := Decode(old)
if err != nil || settings.Version != 3 || settings.Presentation.Speech.Rate != 45 || settings.Presentation.Speech.Module != "fixture" || settings.Presentation.Sound.Volume != 0.3 {
t.Fatal(settings, err)
}
path := filepath.Join(t.TempDir(), "settings.toml")
if err := Save(path, settings); err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(path)
if err != nil || strings.Contains(string(data), "socket =") {
t.Fatal(string(data), err)
}
if _, err := Decode([]byte("version=3\n[presentation.speech]\nsocket='/tmp/unused.sock'\n")); err == nil {
t.Fatal("removed setting accepted in current schema")
}
}