178 lines
6.8 KiB
Go
178 lines
6.8 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 preferences
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"git.stormux.org/storm/fenrir/internal/config"
|
|
"git.stormux.org/storm/fenrir/internal/speech"
|
|
)
|
|
|
|
func TestModuleDiscoveryAndOfflinePreservation(t *testing.T) {
|
|
base := config.Defaults()
|
|
base.Presentation.Speech.Module = "saved-module"
|
|
t.Setenv("SPEECHD_ADDRESS", "unix_socket:"+filepath.Join(t.TempDir(), "missing.sock"))
|
|
offline := Discover_choices(base).Choices(base)
|
|
choices := offline["presentation.speech.module"]
|
|
if len(choices) != 2 || choices[1].ID != "saved-module" || choices[1].Unavailable == "" {
|
|
t.Fatal(choices)
|
|
}
|
|
session, err := New_session(base, nil, offline)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
draft, err := Settings(session, base)
|
|
if err != nil || draft.Presentation.Speech.Module != "saved-module" {
|
|
t.Fatal(draft, err)
|
|
}
|
|
path := filepath.Join(t.TempDir(), "speech.sock")
|
|
listener, err := net.Listen("unix", path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer listener.Close()
|
|
done := make(chan string, 1)
|
|
go func() {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
done <- err.Error()
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
request, err := bufio.NewReader(conn).ReadString('\n')
|
|
if err != nil {
|
|
done <- err.Error()
|
|
return
|
|
}
|
|
fmt.Fprint(conn, "200-fixture-module\r\n200-saved-module\r\n200 OK\r\n")
|
|
done <- request
|
|
}()
|
|
t.Setenv("SPEECHD_ADDRESS", "unix_socket:"+path)
|
|
online := Discover_choices(base).Choices(base)["presentation.speech.module"]
|
|
if request := <-done; request != "LIST OUTPUT_MODULES\r\n" {
|
|
t.Fatal(request)
|
|
}
|
|
if len(online) != 3 || online[1].ID != "fixture-module" || online[2].ID != "saved-module" || online[2].Unavailable != "" {
|
|
t.Fatal(online)
|
|
}
|
|
}
|
|
|
|
func TestDependentSpeechChoicesKeepSavedVoicesAndFilterEspeak(t *testing.T) {
|
|
t.Setenv("LC_ALL", "en_US.UTF-8")
|
|
base := config.Defaults()
|
|
base.Presentation.Speech.Module = "espeak-ng"
|
|
catalog := speech.Catalog{Modules: []string{"espeak-ng", "fixture"}, Voices: map[string][]speech.Voice{
|
|
"espeak-ng": {{Name: "US+Variant", Language: "en-US"}, {Name: "German", Language: "de"}, {Name: "US", Language: "en-US"}},
|
|
"fixture": {{Name: "Fixture", Language: "en"}},
|
|
}}
|
|
discovery := Discovery{Catalog: catalog}
|
|
choices := discovery.Choices(base)
|
|
if got := choices["presentation.speech.voice"]; len(got) != 3 || got[1].ID != "US" || got[2].ID != "US+Variant" {
|
|
t.Fatal(got)
|
|
}
|
|
base.Presentation.Speech.Voice = "German"
|
|
choices = discovery.Choices(base)
|
|
if got := choices["presentation.speech.voice"]; len(got) != 4 || got[3].ID != "German" || got[3].Unavailable != "" {
|
|
t.Fatal("valid saved voice lost", got)
|
|
}
|
|
session, err := New_session(base, nil, choices)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
previous := base
|
|
session.Replace_choices("presentation", "speech.language", interaction_choices(choices["presentation.speech.language"]), "de")
|
|
discovery.Refresh_speech(session, &previous)
|
|
values := session.Values()
|
|
if got := values["presentation.speech.voice"]; len(got.Choices) != 2 || got.Choices[1].ID != "German" || got.Selected != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
session.Replace_choices("presentation", "speech.module", interaction_choices(choices["presentation.speech.module"]), "fixture")
|
|
discovery.Refresh_speech(session, &previous)
|
|
values = session.Values()
|
|
if values["presentation.speech.language"].Selected != 0 || values["presentation.speech.voice"].Selected != 0 || len(values["presentation.speech.voice"].Choices) != 2 {
|
|
t.Fatal(values)
|
|
}
|
|
base.Presentation.Speech.Language = "zz"
|
|
base.Presentation.Speech.Voice = "Missing"
|
|
offline := Discovery{Err: errors.New("fixture offline")}.Choices(base)
|
|
for _, path := range []string{"presentation.speech.module", "presentation.speech.language", "presentation.speech.voice"} {
|
|
if got := offline[path]; len(got) != 2 || got[1].Unavailable == "" {
|
|
t.Fatal(path, got)
|
|
}
|
|
}
|
|
session, err = New_session(base, nil, offline)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
saved, err := Settings(session, base)
|
|
if err != nil || saved.Presentation.Speech != base.Presentation.Speech {
|
|
t.Fatal(saved, err)
|
|
}
|
|
}
|
|
|
|
func TestHardwareDriverChangesHideDispatcherChoicesAndPreserveDevice(t *testing.T) {
|
|
base := config.Defaults()
|
|
base.Presentation.Speech.HardwareDevice = "/dev/fixture-synth"
|
|
base.Presentation.Speech.HardwareBaudRate = 19200
|
|
base.Presentation.Speech.Module = "fixture"
|
|
base.Presentation.Speech.Language = "en"
|
|
base.Presentation.Speech.Voice = "Fixture Voice"
|
|
discovery := Discovery{Driver: "speech-dispatcher", Catalog: speech.Catalog{Modules: []string{"fixture"}, Voices: map[string][]speech.Voice{"fixture": {{Name: "Fixture Voice", Language: "en"}}}}}
|
|
session, err := New_session(base, nil, discovery.Choices(base))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
previous := base
|
|
for _, id := range []string{"dectalk", "litetalk", "doubletalk", "tripletalk", "speech-dispatcher"} {
|
|
var choices []config.Choice
|
|
for _, definition := range config.Definitions() {
|
|
if definition.Path == "presentation.speech.driver" {
|
|
choices = definition.Choices(base)
|
|
}
|
|
}
|
|
session.Replace_choices("presentation", "speech.driver", interaction_choices(choices), id)
|
|
discovery.Refresh_speech(session, &previous)
|
|
draft, err := Settings(session, base)
|
|
if err != nil {
|
|
t.Fatal(id, err)
|
|
}
|
|
selected := draft.Presentation.Speech
|
|
if selected.Driver != id || selected.HardwareDevice != base.Presentation.Speech.HardwareDevice || selected.HardwareBaudRate != 19200 {
|
|
t.Fatal(selected)
|
|
}
|
|
if id != "speech-dispatcher" {
|
|
if selected.Module != "" || selected.Language != "" || selected.Voice != "" {
|
|
t.Fatal("inactive selections saved", selected)
|
|
}
|
|
for _, path := range []string{"presentation.speech.module", "presentation.speech.language", "presentation.speech.voice"} {
|
|
if got := session.Values()[path]; !got.Hidden {
|
|
t.Fatal("dispatcher choices leaked", id, path, got)
|
|
}
|
|
}
|
|
} else if selected.Module != base.Presentation.Speech.Module || selected.Language != base.Presentation.Speech.Language || selected.Voice != base.Presentation.Speech.Voice {
|
|
t.Fatal("returning to Speech Dispatcher lost the draft", selected)
|
|
}
|
|
}
|
|
}
|