Application specific preferences saving now enabled.
This commit is contained in:
1 parent
f88e5b81a2
commit
60abcc37d9
23 files changed
+1004
-248
No files matched your search
@@ -6,10 +6,12 @@ package app
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.stormux.org/storm/fenrir/internal/command"
|
||||
"git.stormux.org/storm/fenrir/internal/config"
|
||||
@@ -50,8 +52,8 @@ func TestOverrideRestoresSynthesizerAndKeepsSaveBlockedUntilCompletion(t *testin
|
||||
if response := request_selection(t, a, "SET speech#voice=Temporary;speech#module=fixture"); response != "" {
|
||||
t.Fatal(response)
|
||||
}
|
||||
if !reflect.DeepEqual(a.settings.saveDraft, baseline) {
|
||||
t.Fatal("temporary voice modified draft")
|
||||
if a.settings.saveDraft.Presentation.Speech.Voice != "Temporary" || !same_speech_selection(a.overrideCurrent.underlying.settings.saveDraft.Presentation.Speech, baseline.Presentation.Speech) {
|
||||
t.Fatal("application voice did not stay in its own draft")
|
||||
}
|
||||
if failed {
|
||||
a.prepareSpeech = func(config.Speech) (speech.Transport, error) { return nil, errors.New("fixture unavailable") }
|
||||
@@ -109,6 +111,19 @@ func override_read(t *testing.T, a *application, snapshot screen.Snapshot) {
|
||||
if err := a.read_screen(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Model the event loop's asynchronous profile-read completion, leaving
|
||||
// speech transitions to the tests which explicitly exercise them.
|
||||
if a.overrideTransition && !a.speechPending {
|
||||
select {
|
||||
case loaded := <-a.overrideLoads:
|
||||
loaded()
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("profile load stalled")
|
||||
}
|
||||
if err := a.read_screen(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationsMenuActivatesOnlyOnEnterAndReleasesCapture(t *testing.T) {
|
||||
@@ -183,7 +198,7 @@ func TestApplicationsMenuPrefixSearch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverrideCannotSaveOrContaminateDraftAndRestoresSession(t *testing.T) {
|
||||
func TestOverrideSavesOwnDraftAndRestoresSession(t *testing.T) {
|
||||
a, voice := persistence_application(t, true)
|
||||
a.handle_command(command.NextQuickValue) // Preserve a pre-existing unsaved adjustment.
|
||||
a.handle_command(command.AutoRead)
|
||||
@@ -202,20 +217,30 @@ func TestOverrideCannotSaveOrContaminateDraftAndRestoresSession(t *testing.T) {
|
||||
if response := a.apply_remote_settings("SET speech#rate=0.77;keyboard#word_echo=True"); response != "" {
|
||||
t.Fatal(response)
|
||||
}
|
||||
if !reflect.DeepEqual(a.settings.saveDraft, baseSettings.saveDraft) {
|
||||
t.Fatal("override modified save draft")
|
||||
if !reflect.DeepEqual(a.overrideCurrent.underlying.settings, baseSettings) {
|
||||
t.Fatal("ordinary draft changed")
|
||||
}
|
||||
profilePath := filepath.Join(filepath.Dir(a.options.ConfigPath), "applications", "ninjam.toml")
|
||||
a.save_settings()
|
||||
if a.saver.busy || voice.messages[len(voice.messages)-1] != "Save unavailable." {
|
||||
t.Fatal("shortcut save admitted")
|
||||
finish_persistence(t, a, false)
|
||||
if !strings.Contains(voice.messages[len(voice.messages)-1], "Ninjam application settings saved") {
|
||||
t.Fatal(voice.messages)
|
||||
}
|
||||
for _, text := range []string{"SETTING SAVE", "SETTING SAVEAS " + a.options.ConfigPath} {
|
||||
if got := runtime_request(a, text); got != "Save unavailable." {
|
||||
t.Fatal(text, got)
|
||||
}
|
||||
doc, err := config.Open_application_document(profilePath, a.overrideCurrent.base)
|
||||
if err != nil || doc.Settings.Presentation.Speech.Rate != a.settings.live.Presentation.Speech.Rate || !doc.Settings.Keyboard.WordEcho {
|
||||
t.Fatal("profile not saved", err)
|
||||
}
|
||||
reply := persistence_request(a, "SAVE")
|
||||
finish_persistence(t, a, false)
|
||||
persistence_reply(t, reply, false)
|
||||
if got := runtime_request(a, "SETTING SAVEAS "+a.options.ConfigPath); got != "SAVEAS is unavailable in application settings" {
|
||||
t.Fatal(got)
|
||||
}
|
||||
a.open_settings()
|
||||
receive_ui(t, a.ui)
|
||||
if !strings.Contains(voice.messages[len(voice.messages)-1], "Ninjam application settings") {
|
||||
t.Fatal(voice.messages)
|
||||
}
|
||||
for range 100 {
|
||||
if control, ok := a.ui.session.Focused_control(); ok && control.Action == "save" {
|
||||
break
|
||||
@@ -223,9 +248,7 @@ func TestOverrideCannotSaveOrContaminateDraftAndRestoresSession(t *testing.T) {
|
||||
a.ui.handle(interaction.Key{Name: "tab"})
|
||||
}
|
||||
a.ui.handle(interaction.Key{Name: "enter"})
|
||||
if !a.ui.active || a.saver.busy || voice.messages[len(voice.messages)-1] != "Save unavailable." {
|
||||
t.Fatal("menu save admitted", voice.messages)
|
||||
}
|
||||
finish_persistence(t, a, false)
|
||||
a.ui.close()
|
||||
a.select_override("default")
|
||||
if !reflect.DeepEqual(a.settings, baseSettings) || a.quick.revision != baseQuick || !reflect.DeepEqual(a.engine.Capture_session(), baseRuntime) {
|
||||
@@ -355,3 +378,132 @@ func TestActivationRejectsPendingOperationsAndRestartStartsWithoutOverride(t *te
|
||||
t.Fatal("restart retained override", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationProfileReloadAndDefaultInheritance(t *testing.T) {
|
||||
for _, terminalMode := range []bool{false, true} {
|
||||
a, _ := persistence_application(t, terminalMode)
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
a.select_override("ninjam")
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
a.handle_command(command.NextQuickValue)
|
||||
savedRate := a.settings.live.Presentation.Speech.Rate
|
||||
a.save_settings()
|
||||
// A terminal switch must wait for the admitted application write.
|
||||
override_read(t, a, override_screen(3, "other terminal"))
|
||||
if a.overrideCurrent == nil {
|
||||
t.Fatal("save ownership lost before completion")
|
||||
}
|
||||
finish_persistence(t, a, false)
|
||||
override_read(t, a, override_screen(3, "other terminal"))
|
||||
if a.overrideCurrent != nil || a.settings.live.Presentation.Speech.Rate == savedRate {
|
||||
t.Fatal("application leaked across terminal")
|
||||
}
|
||||
// Change an unrelated ordinary setting, then discard/reselect the adapter.
|
||||
if text := a.apply_remote_settings("SET speech#pitch=0.6"); text != "" {
|
||||
t.Fatal(text)
|
||||
}
|
||||
pitch := a.settings.live.Presentation.Speech.Pitch
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
a.select_override("default")
|
||||
a.select_override("ninjam")
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
if a.settings.live.Presentation.Speech.Rate != savedRate || a.settings.live.Presentation.Speech.Pitch != pitch {
|
||||
t.Fatal("saved rate or new inherited pitch missing", a.settings.live.Presentation.Speech)
|
||||
}
|
||||
if text := a.apply_remote_settings("SET speech#rate=0.1"); text != "" {
|
||||
t.Fatal(text)
|
||||
}
|
||||
if text := a.reset_remote_settings(); text != "" {
|
||||
t.Fatal(text)
|
||||
}
|
||||
if a.settings.live.Presentation.Speech.Rate != savedRate {
|
||||
t.Fatal("RESET did not restore profile")
|
||||
}
|
||||
before := a.settings
|
||||
if text := a.apply_remote_settings("SET speech#rate=0.2;screen#ignore_screen=1"); text == "" || !reflect.DeepEqual(a.settings, before) {
|
||||
t.Fatal("global remote change was not rejected atomically")
|
||||
}
|
||||
override_read(t, a, override_screen(1, "[Username@host directory] $"))
|
||||
if a.overrideCurrent != nil || a.settings.live.Presentation.Speech.Rate == savedRate {
|
||||
t.Fatal("exit did not restore default")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationProfileVoiceLoadAndFailureLeaveDefaultIntact(t *testing.T) {
|
||||
for _, failure := range []string{"", "invalid", "unavailable"} {
|
||||
a, _, path := selection_application(t, true)
|
||||
profilePath := filepath.Join(filepath.Dir(path), "applications", "ninjam.toml")
|
||||
if err := os.MkdirAll(filepath.Dir(profilePath), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data := "[presentation.speech]\nvoice = 'Application voice'\nrate = 12\n"
|
||||
if failure == "invalid" {
|
||||
data = "[presentation.speech]\nrate = 999\n"
|
||||
}
|
||||
if err := os.WriteFile(profilePath, []byte(data), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.prepareSpeech = func(config.Speech) (speech.Transport, error) {
|
||||
if failure == "unavailable" {
|
||||
return nil, errors.New("voice unavailable")
|
||||
}
|
||||
return &preparedVoice{}, nil
|
||||
}
|
||||
baseline := a.settings
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
a.select_override("ninjam")
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
if a.speechPending {
|
||||
finish_selection(t, a)
|
||||
}
|
||||
if failure != "" {
|
||||
if a.overrideCurrent != nil || !reflect.DeepEqual(a.settings, baseline) || a.saves_unavailable() {
|
||||
t.Fatal("failed profile changed ordinary settings")
|
||||
}
|
||||
continue
|
||||
}
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
if a.settings.live.Presentation.Speech.Voice != "Application voice" || a.settings.live.Presentation.Speech.Rate != 12 {
|
||||
t.Fatal("profile voice not applied")
|
||||
}
|
||||
a.select_override("default")
|
||||
finish_selection(t, a)
|
||||
if !reflect.DeepEqual(a.settings, baseline) {
|
||||
t.Fatal("default voice not restored")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationMenuPreservesSessionTogglesAndLaunchChoices(t *testing.T) {
|
||||
a, _ := persistence_application(t, true)
|
||||
a.settings.saveDraft.Keyboard.Layout = "laptop"
|
||||
a.settings.saveDraft.Presentation.Speech.Driver = "dectalk"
|
||||
a.settings.saveDraft.Presentation.Speech.Voice = "Next launch voice"
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
a.select_override("ninjam")
|
||||
override_read(t, a, override_screen(1, "ninjam"))
|
||||
a.handle_command(command.Mute)
|
||||
a.handle_command(command.AutoRead)
|
||||
a.open_settings()
|
||||
receive_ui(t, a.ui)
|
||||
for range 100 {
|
||||
if control, ok := a.ui.session.Focused_control(); ok && control.Action == "save" {
|
||||
break
|
||||
}
|
||||
a.ui.handle(interaction.Key{Name: "tab"})
|
||||
}
|
||||
a.ui.handle(interaction.Key{Name: "enter"})
|
||||
finish_persistence(t, a, false)
|
||||
if !a.muted || a.engine.Automatic_reading() || a.settings.live.Keyboard.Layout != "desktop" || a.settings.live.Presentation.Speech.Driver != "speech-dispatcher" || a.settings.live.Presentation.Speech.Voice != "" {
|
||||
t.Fatal("menu changed session toggles or launch-only choices")
|
||||
}
|
||||
data, err := os.ReadFile(a.overrideCurrent.path)
|
||||
if err != nil || strings.Contains(string(data), "enabled") || strings.Contains(string(data), "automatic") || strings.Contains(string(data), "driver") || strings.Contains(string(data), "layout") || strings.Contains(string(data), "voice") {
|
||||
t.Fatal("menu saved inherited/session/launch settings", string(data), err)
|
||||
}
|
||||
a.select_override("default")
|
||||
if a.settings.saveDraft.Keyboard.Layout != "laptop" || a.settings.saveDraft.Presentation.Speech.Driver != "dectalk" {
|
||||
t.Fatal("ordinary next-launch choices lost")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user