120 lines
4.9 KiB
Go
120 lines
4.9 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.stormux.org/storm/fenrir/internal/command"
|
|
"git.stormux.org/storm/fenrir/internal/config"
|
|
"git.stormux.org/storm/fenrir/internal/control"
|
|
"git.stormux.org/storm/fenrir/internal/input"
|
|
"git.stormux.org/storm/fenrir/internal/screen"
|
|
)
|
|
|
|
func TestMenuRemoteAndQuickSavePreserveNextLaunchChoices(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
path := filepath.Join(t.TempDir(), "settings.toml")
|
|
base := config.Defaults()
|
|
source := &fakeSource{snapshot: screen.Snapshot{Terminal: 1, Active: true, Rows: 1, Cols: 5, Cells: []rune("alpha")}}
|
|
voice := &fakeVoice{}
|
|
keys := &fakeKeys{poll: func(bool, func(input.Decision)) error { return nil }}
|
|
a, err := new_application(ctx, source, keys, voice, RunOptions{Settings: base, ConfigPath: path})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer a.ui.close()
|
|
|
|
// Exercise the successful menu-save callback with both live and next-launch
|
|
// choices, then interleave session commands, remote changes and a quick save.
|
|
menuSaved := base
|
|
menuSaved.Keyboard.Layout = "laptop"
|
|
menuSaved.Presentation.Speech.Module = "next-module"
|
|
menuSaved.Presentation.Speech.Rate = 60
|
|
menuSaved.Review.LineBreak = false
|
|
menuSaved.Presentation.Sound.Driver = "alsa"
|
|
menuSaved.Presentation.Sound.Theme = "fixture-pack"
|
|
menuSaved.Presentation.Sound.Device = "null"
|
|
if err := config.Save(path, menuSaved); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
a.apply_saved_settings(menuSaved, false)
|
|
if a.settings.live.Keyboard.Layout != base.Keyboard.Layout || a.settings.live.Presentation.Speech.Module != base.Presentation.Speech.Module || a.settings.live.Review != base.Review || a.settings.live.Presentation.Sound != base.Presentation.Sound {
|
|
t.Fatal("next-launch choices changed the running configuration")
|
|
}
|
|
if len(voice.parameters) == 0 || voice.parameters[len(voice.parameters)-1].Rate != 60 {
|
|
t.Fatal("menu speech adjustment did not apply live", voice.parameters)
|
|
}
|
|
for _, id := range []command.ID{command.Mute, command.AutoRead, command.ToggleAttributes} {
|
|
a.handle_input(input.Decision{Command: id})
|
|
}
|
|
for _, request := range []struct {
|
|
text string
|
|
want string
|
|
}{
|
|
{"SETTING SET speech#enabled=True;unknown#setting=True", "Unsupported remote setting"},
|
|
{"SETTING SET keyboard#word_echo=True", ""},
|
|
} {
|
|
replies := make(chan string, 1)
|
|
a.remote_request(control.Request{UID: 0, Text: request.text, Reply: replies})
|
|
if reply := <-replies; !strings.HasPrefix(reply, request.want) || request.want == "" && reply != "" {
|
|
t.Fatal("unexpected remote response", reply)
|
|
}
|
|
if !voice.muted || a.engine.Automatic_reading() {
|
|
t.Fatal("remote assignment changed unrelated session state")
|
|
}
|
|
}
|
|
for _, id := range []command.ID{command.NextQuickValue, command.SaveSettings, command.Quit} {
|
|
a.handle_input(input.Decision{Command: id})
|
|
}
|
|
if err := a.loop(nil); err != nil || ctx.Err() != nil {
|
|
t.Fatal("save and quit failed", err, ctx.Err())
|
|
}
|
|
saved, _, err := config.Load(path, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := menuSaved
|
|
want.Keyboard.WordEcho = true
|
|
want.Presentation.Speech.Rate = 70
|
|
if saved.Keyboard.Layout != want.Keyboard.Layout || saved.Presentation.Speech.Module != want.Presentation.Speech.Module || saved.Review != want.Review || saved.Presentation.Speech.Rate != want.Presentation.Speech.Rate || !saved.Keyboard.WordEcho || saved.Presentation.Sound != want.Presentation.Sound {
|
|
t.Fatal("mixed settings paths lost saved choices", saved)
|
|
}
|
|
if !saved.Presentation.Speech.Enabled || !saved.Reading.Automatic || saved.Reading.Attributes != base.Reading.Attributes {
|
|
t.Fatal("session overrides leaked into the save", saved)
|
|
}
|
|
freshVoice := &fakeVoice{}
|
|
fresh, err := new_application(ctx, source, &fakeKeys{}, freshVoice, RunOptions{Settings: saved})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer fresh.ui.close()
|
|
if err := fresh.read_screen(); err != nil || !slices.Contains(freshVoice.messages, "alpha") {
|
|
t.Fatal("saved defaults did not restore startup reading", err, freshVoice.messages)
|
|
}
|
|
}
|
|
|
|
func TestRemoteCannotEnableSpeechWithoutStartupConnection(t *testing.T) {
|
|
base := config.Defaults()
|
|
base.Presentation.Speech.Enabled = false
|
|
a, err := new_application(context.Background(), &fakeSource{}, &fakeKeys{}, disabledVoice{}, RunOptions{Settings: base})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer a.ui.close()
|
|
response := a.apply_remote_settings("SET speech#enabled=True;keyboard#word_echo=True")
|
|
if response != "Speech was disabled at startup; enable it in settings and relaunch" {
|
|
t.Fatal(response)
|
|
}
|
|
if !a.muted || a.settings.live.Presentation.Speech.Enabled || a.settings.live.Keyboard.WordEcho || a.settings.saveDraft.Presentation.Speech.Enabled || a.settings.saveDraft.Keyboard.WordEcho {
|
|
t.Fatal("rejected request partially changed live or saved settings")
|
|
}
|
|
}
|