// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "context" "os" "path/filepath" "reflect" "strings" "testing" "git.stormux.org/storm/fenrir/internal/command" "git.stormux.org/storm/fenrir/internal/config" "git.stormux.org/storm/fenrir/internal/interaction" "git.stormux.org/storm/fenrir/internal/presentation" "git.stormux.org/storm/fenrir/internal/punctuation" "git.stormux.org/storm/fenrir/internal/screen" ) func TestTextProcessingMenuCommitCancelAndFailedSave(t *testing.T) { processor, _, err := punctuation.Load([]string{"../../config/punctuation/default.toml"}) if err != nil { t.Fatal(err) } for _, action := range []string{"save", "cancel", "failed save"} { t.Run(action, func(t *testing.T) { path := filepath.Join(t.TempDir(), "settings.toml") if err := os.WriteFile(path, []byte("version=3\n[reading]\n"), 0600); err != nil { t.Fatal(err) } settings := config.Defaults() text := ":) :) #####" source := &fakeSource{snapshot: screen.Snapshot{Terminal: 1, Active: true, Rows: 1, Cols: len(text), Cells: []rune(text)}} voice := &fakeVoice{} a, err := new_application(context.Background(), source, &fakeKeys{}, voice, RunOptions{Settings: settings, ConfigPath: path, Punctuation: processor}) if err != nil { t.Fatal(err) } defer a.ui.close() if err := a.read_screen(); err != nil { t.Fatal(err) } if got := strings.Join(strings.Fields(voice.messages[len(voice.messages)-1]), " "); got != "smile smile hash hash hash hash hash" { t.Fatal("automatic reading did not use default substitutions", voice.messages) } if a.voice.last != text { t.Fatal("automatic reading changed clipboard echo", a.voice.last) } a.open_settings() receive_ui(t, a.ui) focus := func(want string) { t.Helper() for range 40 { a.ui.handle(interaction.Key{Name: "tab"}) if voice.messages[len(voice.messages)-1] == want { return } } t.Fatal("control not found", want, voice.messages) } focus("Emoticons checked") a.ui.handle(interaction.Key{Name: "space"}) focus("Repeated symbols unchecked") a.ui.handle(interaction.Key{Name: "space"}) focus("Repeated symbols minimum length 4") a.ui.handle(interaction.Key{Name: "up"}) if a.settings.live.Reading != settings.Reading { t.Fatal("draft changed runtime processing") } if action == "cancel" { a.ui.handle(interaction.Key{Name: "escape"}) } else { if action == "failed save" { if err := os.WriteFile(path, []byte("version=3\n[reading]\nemoticons=false\n"), 0600); err != nil { t.Fatal(err) } } focus("OK button") a.ui.handle(interaction.Key{Name: "enter"}) finish_persistence(t, a, action == "failed save") if action == "failed save" { if !a.ui.active || !strings.HasPrefix(voice.messages[len(voice.messages)-1], "Could not save settings:") { t.Fatal("failed save lost the draft", voice.messages) } a.ui.handle(interaction.Key{Name: "escape"}) } } voice.messages = nil a.voice.Say(text) if a.voice.last != text { t.Fatal("processing changed raw clipboard echo", a.voice.last) } if action != "save" { if a.settings.live.Reading != settings.Reading || strings.Join(strings.Fields(voice.messages[0]), " ") != "smile smile hash hash hash hash hash" { t.Fatal("cancel or failed save changed processing", voice.messages) } return } if strings.Join(strings.Fields(voice.messages[0]), " ") != ": : 5 number" { t.Fatal("saved processing did not apply live", voice.messages) } saved, _, err := config.Load(path, nil) if err != nil || saved.Reading.Emoticons || !saved.Reading.RepeatedSymbols || saved.Reading.RepeatedSymbolsMin != 5 { t.Fatal(saved.Reading, err) } freshVoice := &fakeVoice{} fresh, err := new_application(context.Background(), source, &fakeKeys{}, freshVoice, RunOptions{Settings: saved, Punctuation: processor}) if err != nil { t.Fatal(err) } defer fresh.ui.close() if err := fresh.read_screen(); err != nil || strings.Join(strings.Fields(freshVoice.messages[len(freshVoice.messages)-1]), " ") != ": : 5 number" { t.Fatal("fresh reader lost saved processing", freshVoice.messages, err) } voice.messages = nil a.review_command(command.CurrentLine) if strings.Join(strings.Fields(voice.messages[0]), " ") != ": : 5 number" { t.Fatal("review bypassed processing", voice.messages) } voice.messages = nil a.voice.say_spelling(presentation.Spelling{Text: "#####"}) if !reflect.DeepEqual(voice.messages, []string{"hash", "hash", "hash", "hash", "hash"}) || a.voice.last != "#####" { t.Fatal("summary changed spelling or raw copy", voice.messages, a.voice.last) } voice.messages = nil a.voice.say_spelling(presentation.Spelling{Text: ":)", Phonetic: true}) if !reflect.DeepEqual(voice.messages, []string{"colon", "right paren"}) { t.Fatal("emoticon changed phonetic feedback", voice.messages) } }) } }