// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "context" "os" "path/filepath" "strings" "testing" "time" "git.stormux.org/storm/fenrir/internal/command" "git.stormux.org/storm/fenrir/internal/config" "git.stormux.org/storm/fenrir/internal/datetime" "git.stormux.org/storm/fenrir/internal/input" "git.stormux.org/storm/fenrir/internal/interaction" "git.stormux.org/storm/fenrir/internal/screen" ) func TestTimeDateMenuAppliesOnlyAfterSave(t *testing.T) { for _, action := range []string{"save", "cancel", "failed save"} { t.Run(action, func(t *testing.T) { path := filepath.Join(t.TempDir(), "settings.toml") // Put this group first to exercise its controls directly. original := "version=3\n[presentation.time_and_date]\ntime_format='24-hour-seconds'\ndate_format='numbers-ymd'\n" if err := os.WriteFile(path, []byte(original), 0600); err != nil { t.Fatal(err) } settings, err := config.Decode([]byte(original)) if err != nil { t.Fatal(err) } source := &fakeSource{snapshot: screen.Snapshot{Terminal: 1, Active: true}} keys, voice := &fakeKeys{events: make(chan error, 4)}, &fakeVoice{} keys.onCapture = func(enabled bool) { if !enabled { keys.events <- nil } } voice.onSay = func(text string) { if strings.HasPrefix(text, "Fenrir Menu.") || strings.HasPrefix(text, "Could not save settings:") { keys.events <- nil } } step := 0 keys.poll = func(_ bool, handle func(input.Decision)) error { step++ key := func(name string, shift bool) { handle(input.Decision{UI: &interaction.Key{Name: name, Shift: shift}}) } switch step { case 1: handle(input.Decision{Command: command.FenrirMenu}) case 2: key("tab", false) key("down", false) // 24-hour-seconds -> 24-hour-seconds-words. key("tab", false) key("down", false) // numbers-ymd -> full-dm. if action == "cancel" { key("escape", false) break } if action == "failed save" { if err := os.WriteFile(path, []byte(original+"# external edit\n"), 0600); err != nil { t.Fatal(err) } } for range 4 { key("tab", true) } key("enter", false) case 3: if action == "failed save" { key("escape", false) } formats := settings.Presentation.DateTime if action == "save" { formats.TimeFormat, formats.DateFormat = "24-hour-seconds-words", "full-dm" } for _, id := range []command.ID{command.Time, command.Date} { format, value := datetime.Format_time, formats.TimeFormat if id == command.Date { format, value = datetime.Format_date, formats.DateFormat } before, _ := format(value, time.Now()) handle(input.Decision{Command: id, Interrupt: true}) after, _ := format(value, time.Now()) got := voice.messages[len(voice.messages)-1] if got != before && got != after { t.Fatal("running format", id, got, before, after) } } handle(input.Decision{Command: command.Quit}) } return nil } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() if err := run(ctx, source, keys, voice, nil, RunOptions{Settings: settings, ConfigPath: path}); err != nil || step != 3 { t.Fatal(err, step, voice.messages) } loaded, _, err := config.Load(path, nil) want := settings.Presentation.DateTime if action == "save" { want.TimeFormat, want.DateFormat = "24-hour-seconds-words", "full-dm" } if err != nil || loaded.Presentation.DateTime != want { t.Fatal("saved formats", loaded.Presentation.DateTime, err) } }) } }