169 lines
6.3 KiB
Go
169 lines
6.3 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package app
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.stormux.org/storm/fenrir/internal/command"
|
|
"git.stormux.org/storm/fenrir/internal/config"
|
|
"git.stormux.org/storm/fenrir/internal/input"
|
|
"git.stormux.org/storm/fenrir/internal/interaction"
|
|
)
|
|
|
|
func admit_menu_save(t *testing.T, a *application) {
|
|
t.Helper()
|
|
a.open_settings()
|
|
receive_ui(t, a.ui)
|
|
a.ui.handle(interaction.Key{Name: "tab"})
|
|
a.ui.handle(interaction.Key{Name: "tab"})
|
|
a.ui.handle(interaction.Key{Name: "right"}) // Rate 24 -> 25.
|
|
a.ui.action("save", a.ui.session)
|
|
if !a.saver.busy || a.ui.session != nil {
|
|
t.Fatal("menu save was not admitted")
|
|
}
|
|
}
|
|
|
|
func committed_menu_result(t *testing.T, a *application) error {
|
|
t.Helper()
|
|
select {
|
|
case err := <-a.saver.results:
|
|
return err
|
|
case <-time.After(time.Second):
|
|
t.Fatal("menu save worker stalled")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func TestMenuCommitSurvivesCloseAndHandoff(t *testing.T) {
|
|
for _, terminalMode := range []bool{false, true} {
|
|
for _, closeAction := range []string{"escape", "terminal", "focus", "other menu"} {
|
|
t.Run(closeAction+map[bool]string{false: "/tty", true: "/x11"}[terminalMode], func(t *testing.T) {
|
|
a, voice := persistence_application(t, terminalMode)
|
|
admit_menu_save(t, a)
|
|
err := committed_menu_result(t, a) // Gate completion after disk commit.
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
switch closeAction {
|
|
case "terminal", "focus":
|
|
source := a.source.(*fakeSource)
|
|
if closeAction == "terminal" {
|
|
source.snapshot.Terminal++
|
|
} else {
|
|
source.snapshot.Active = false
|
|
}
|
|
if err := a.read_screen(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
default:
|
|
a.ui.handle(interaction.Key{Name: "escape"})
|
|
}
|
|
if a.ui.active || a.keyboards.(*fakeKeys).captures[1] {
|
|
t.Fatal("close did not release capture immediately")
|
|
}
|
|
if closeAction == "other menu" {
|
|
a.ui.begin("Other menu")
|
|
}
|
|
generation, speechCount := a.ui.generation, len(voice.messages)
|
|
a.settings_save_result(err)
|
|
saved, _, err := config.Load(a.options.ConfigPath, nil)
|
|
if err != nil || saved.Presentation.Speech.Rate != 25 || a.settings.live.Presentation.Speech.Rate != 25 || !reflect.DeepEqual(a.settings.saveDraft, saved) || !reflect.DeepEqual(a.settings.resetBase, saved) {
|
|
t.Fatal("committed menu settings diverged", saved, a.settings, err)
|
|
}
|
|
if a.ui.generation != generation || len(voice.messages) != speechCount || a.ui.active != (closeAction == "other menu") {
|
|
t.Fatal("stale save changed modal presentation")
|
|
}
|
|
configured := len(voice.parameters)
|
|
a.settings_save_result(nil)
|
|
if len(voice.parameters) != configured {
|
|
t.Fatal("duplicate result applied twice")
|
|
}
|
|
if closeAction == "other menu" {
|
|
a.ui.close()
|
|
}
|
|
a.open_settings()
|
|
receive_ui(t, a.ui)
|
|
if a.ui.session.Values()["presentation.speech.rate"].Number != 25 {
|
|
t.Fatal("reopened form lost the commit")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMenuCommitPreservesNewerOverridesAndCoordinatesSaves(t *testing.T) {
|
|
for _, terminalMode := range []bool{false, true} {
|
|
a, _ := persistence_application(t, terminalMode)
|
|
admit_menu_save(t, a)
|
|
err := committed_menu_result(t, a)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
a.ui.close()
|
|
a.open_settings()
|
|
if a.ui.active {
|
|
t.Fatal("settings menu reopened during an admitted save")
|
|
}
|
|
persistence_reply(t, persistence_request(a, "SAVE"), true)
|
|
persistence_reply(t, persistence_request(a, "SAVEAS ignored.toml"), true)
|
|
persistence_reply(t, persistence_request(a, "RESET"), true)
|
|
a.save_settings() // Cannot replace the pending menu snapshot.
|
|
persistence_reply(t, persistence_request(a, "SET speech#pitch=0.8;keyboard#word_echo=True"), false)
|
|
for _, id := range []command.ID{command.NextQuickValue, command.PreviousQuickValue, command.ProgressMonitor, command.Mute, command.AutoRead} {
|
|
a.handle_input(input.Decision{Command: id})
|
|
}
|
|
// Returning rate to its old value still counts as a later adjustment.
|
|
a.settings_save_result(err)
|
|
if a.settings.live.Presentation.Speech.Rate != 24 || a.settings.live.Presentation.Speech.Pitch != 60 || !a.settings.live.Keyboard.WordEcho || a.settings.live.Presentation.Progress.Enabled == a.settings.resetBase.Presentation.Progress.Enabled || !a.muted || a.engine.Automatic_reading() || !a.quick.changed {
|
|
t.Fatal("menu completion overwrote a later/session change", a.settings.live)
|
|
}
|
|
if a.settings.saveDraft.Presentation.Speech.Rate != 24 || a.settings.saveDraft.Presentation.Speech.Pitch != 60 || !a.settings.saveDraft.Keyboard.WordEcho || a.settings.resetBase.Presentation.Speech.Rate != 25 {
|
|
t.Fatal("save draft or reset baseline diverged", a.settings)
|
|
}
|
|
persistence_reply(t, persistence_request(a, "RESET"), false)
|
|
if a.settings.live.Presentation.Speech.Rate != 25 || a.settings.live.Presentation.Speech.Pitch != a.settings.resetBase.Presentation.Speech.Pitch || a.settings.live.Keyboard.WordEcho || !a.muted || a.engine.Automatic_reading() {
|
|
t.Fatal("RESET did not restore committed values and preserve session toggles")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMenuFailureAfterCloseIsSilentAndDoesNotCommit(t *testing.T) {
|
|
a, voice := persistence_application(t, true)
|
|
base := a.settings.resetBase
|
|
a.open_settings()
|
|
receive_ui(t, a.ui)
|
|
external := base
|
|
external.Presentation.Speech.Rate = 70
|
|
if err := config.Save(a.options.ConfigPath, external); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
a.ui.action("save", a.ui.session)
|
|
err := committed_menu_result(t, a)
|
|
if err == nil {
|
|
t.Fatal("external edit was overwritten")
|
|
}
|
|
a.ui.close()
|
|
count := len(voice.messages)
|
|
a.settings_save_result(err)
|
|
if a.ui.active || a.saver.busy || len(voice.messages) != count || !reflect.DeepEqual(a.settings.resetBase, base) || !reflect.DeepEqual(a.settings.saveDraft, base) {
|
|
t.Fatal("closed failure changed state or presented into another session")
|
|
}
|
|
}
|
|
|
|
func TestQuitWaitsForMenuCommitAfterClose(t *testing.T) {
|
|
a, _ := persistence_application(t, true)
|
|
admit_menu_save(t, a)
|
|
a.ui.close()
|
|
a.handle_input(input.Decision{Command: command.Quit})
|
|
if a.quitting || !a.quitAfterSave {
|
|
t.Fatal("quit did not wait for menu save")
|
|
}
|
|
if err := a.loop(nil); err != nil || a.ctx.Err() != nil || a.settings.resetBase.Presentation.Speech.Rate != 25 || a.saver.busy {
|
|
t.Fatal("quit lost admitted commit", err, a.ctx.Err())
|
|
}
|
|
}
|