125 lines
4.3 KiB
Go
125 lines
4.3 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"strings"
|
|
"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"
|
|
"git.stormux.org/storm/fenrir/internal/presentation"
|
|
"git.stormux.org/storm/fenrir/internal/screen"
|
|
)
|
|
|
|
func prompt_snapshot(lines ...string) screen.Snapshot {
|
|
result := screen.Snapshot{Terminal: 1, Active: true, Rows: len(lines), Cols: 80, Y: len(lines) - 1}
|
|
for _, line := range lines {
|
|
cells := make([]rune, result.Cols)
|
|
for index := range cells {
|
|
cells[index] = ' '
|
|
}
|
|
copy(cells, []rune(line))
|
|
result.Cells = append(result.Cells, cells...)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func TestPromptRestorationDiscardsBatchAndPreservesModes(t *testing.T) {
|
|
for _, mode := range []string{"sound", "speech", "missing sound", "automatic off", "muted", "menu"} {
|
|
for _, line := range []string{"$", "Password:", "Worked for 19s • 7:46 PM", "Worked for 3m 35s • 8:05 PM", "CUSTOM READY"} {
|
|
t.Run(mode+"/"+line, func(t *testing.T) {
|
|
settings := config.Defaults()
|
|
settings.Presentation.Sound.Enabled = mode != "speech"
|
|
settings.Reading.Automatic = mode != "automatic off"
|
|
settings.Prompt.ExactMatches = []string{"CUSTOM READY"}
|
|
source := &fakeSource{snapshot: prompt_snapshot("old", "working"), events: make(chan error, 1)}
|
|
keys := &fakeKeys{events: make(chan error, 1)}
|
|
voice, sounds := &fakeVoice{}, &fakeSounds{}
|
|
if mode == "missing sound" {
|
|
sounds.missing = map[presentation.Cue]bool{presentation.SpeechOn: true}
|
|
}
|
|
reads, polls := 0, 0
|
|
source.afterRead = func() {
|
|
reads++
|
|
if reads > 1 {
|
|
keys.events <- nil
|
|
}
|
|
}
|
|
keys.poll = func(_ bool, handle func(input.Decision)) error {
|
|
polls++
|
|
switch polls {
|
|
case 1:
|
|
handle(input.Decision{Command: command.TempDisableSpeech, Interrupt: true, Keypress: true})
|
|
if mode == "muted" {
|
|
handle(input.Decision{Command: command.Mute})
|
|
}
|
|
if mode == "menu" {
|
|
// Menu loading is asynchronous. Start the prompt trial
|
|
// only after its opening announcement has been delivered.
|
|
voice.onSay = func(text string) {
|
|
if !strings.HasPrefix(text, "Fenrir Menu.") {
|
|
return
|
|
}
|
|
voice.onSay = nil
|
|
voice.messages, sounds.cues = nil, nil
|
|
source.snapshot = prompt_snapshot("discard this backlog", "Worked for 19s • 7:4")
|
|
source.events <- nil
|
|
}
|
|
handle(input.Decision{Command: command.FenrirMenu})
|
|
return nil
|
|
}
|
|
voice.messages, sounds.cues = nil, nil
|
|
source.snapshot = prompt_snapshot("discard this backlog", "Worked for 19s • 7:4")
|
|
source.events <- nil
|
|
case 2:
|
|
if len(voice.messages) != 0 || len(sounds.cues) != 0 {
|
|
t.Fatal("incomplete line restored speech", voice.messages, sounds.cues)
|
|
}
|
|
source.snapshot = prompt_snapshot("also discard this batch", line)
|
|
source.events <- nil
|
|
case 3:
|
|
var wantSpeech []string
|
|
var wantCues []presentation.Cue
|
|
if mode != "muted" && mode != "menu" {
|
|
if mode == "speech" || mode == "missing sound" {
|
|
wantSpeech = append(wantSpeech, "Speech restored")
|
|
} else {
|
|
wantCues = []presentation.Cue{presentation.SpeechOn}
|
|
}
|
|
if mode != "automatic off" {
|
|
wantSpeech = append(wantSpeech, line)
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(voice.messages, wantSpeech) || !reflect.DeepEqual(sounds.cues, wantCues) {
|
|
t.Fatal(voice.messages, sounds.cues, "want", wantSpeech, wantCues)
|
|
}
|
|
voice.messages, sounds.cues = nil, nil
|
|
source.events <- nil // unchanged trigger must not repeat
|
|
case 4:
|
|
if len(voice.messages) != 0 || len(sounds.cues) != 0 {
|
|
t.Fatal("repeated restoration", voice.messages, sounds.cues)
|
|
}
|
|
if mode == "menu" {
|
|
handle(input.Decision{UI: &interaction.Key{Name: "escape"}})
|
|
}
|
|
handle(input.Decision{Command: command.Quit})
|
|
}
|
|
return nil
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
if err := run(ctx, source, keys, voice, nil, RunOptions{Settings: settings, Sounds: sounds}); err != nil || polls != 4 {
|
|
t.Fatal(err, polls)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|