// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "context" "errors" "git.stormux.org/storm/cthulhu/internal/config" "git.stormux.org/storm/cthulhu/internal/input" "git.stormux.org/storm/cthulhu/internal/preferences" "log/slog" "path/filepath" "reflect" "testing" ) type bindingRecorder struct { values []input.BindingOverride calls int err error } func (recorder *bindingRecorder) SetKeyBindings(_ context.Context, values []input.BindingOverride) error { recorder.calls++ if recorder.err != nil { return recorder.err } recorder.values = input.CloneBindingOverrides(values) return nil } func TestPreferencesKeyBindingsApplySaveResetAndFailure(t *testing.T) { settings := config.DefaultSettings() settings.Path = filepath.Join(t.TempDir(), "settings.toml") worker := newPreferencesWorker(slog.New(slog.DiscardHandler), settings, nil) recorder := &bindingRecorder{} worker.bindings = recorder values := preferences.FromSettings(settings) values.KeyBindings = []input.BindingOverride{{Layout: input.DesktopKeyboardLayout, Scope: "global", Command: input.Preferences, Keys: []string{"Cthulhu+F8"}}} recorder.err = errors.New("grab conflict") response := worker.apply(context.Background(), preferences.Request{Values: values, Save: true}) if response.Accepted || len(worker.settings.KeyBindings) != 0 { t.Fatal("failed update committed") } recorder.err = nil response = worker.apply(context.Background(), preferences.Request{Values: values, Save: true}) if !response.Accepted || !reflect.DeepEqual(values.KeyBindings, recorder.values) { t.Fatalf("apply: %+v", response) } loaded, err := config.Load(settings.Path) if err != nil || !reflect.DeepEqual(values.KeyBindings, loaded.KeyBindings) { t.Fatalf("save: %v", err) } calls := recorder.calls values.KeyBindings[0].Keys = []string{"Cthulhu+q"} response = worker.apply(context.Background(), preferences.Request{Values: values}) if response.Accepted || recorder.calls != calls { t.Fatal("invalid bindings reached backend") } values.KeyBindings[0].Keys = []string{"Cthulhu+F9"} rate := 70.0 values.Rate = &rate response = worker.apply(context.Background(), preferences.Request{Values: values}) if response.Accepted || recorder.values[0].Keys[0] != "Cthulhu+F8" { t.Fatal("later backend failure did not restore keys") } values.Rate = nil values.KeyBindings = nil response = worker.apply(context.Background(), preferences.Request{Values: values, Save: true}) if !response.Accepted || len(recorder.values) != 0 { t.Fatal("reset was not applied") } } type keyboardRecorder struct { bindingRecorder layout input.KeyboardLayout modifiers []string } func (recorder *keyboardRecorder) SetKeyboardConfiguration(ctx context.Context, layout input.KeyboardLayout, modifiers []string, overrides []input.BindingOverride) error { if err := recorder.SetKeyBindings(ctx, overrides); err != nil { return err } recorder.layout = layout recorder.modifiers = append([]string(nil), modifiers...) return nil } func TestPreferencesKeyboardConfigurationApplySaveRollback(t *testing.T) { settings := config.DefaultSettings() settings.Path = filepath.Join(t.TempDir(), "settings.toml") worker := newPreferencesWorker(slog.New(slog.DiscardHandler), settings, nil) recorder := &keyboardRecorder{} worker.bindings = recorder values := preferences.FromSettings(settings) values.KeyboardLayout = 2 values.CthulhuModifierKeys = []string{"Caps_Lock"} recorder.err = errors.New("modifier grab conflict") response := worker.apply(context.Background(), preferences.Request{Values: values, Save: true}) if response.Accepted || worker.settings.KeyboardLayout != 1 { t.Fatal("failed modifier update committed") } recorder.err = nil response = worker.apply(context.Background(), preferences.Request{Values: values, Save: true}) if !response.Accepted || recorder.layout != input.LaptopKeyboardLayout || !reflect.DeepEqual(recorder.modifiers, values.CthulhuModifierKeys) { t.Fatalf("apply: %+v", response) } loaded, err := config.Load(settings.Path) if err != nil || loaded.KeyboardLayout != 2 || !reflect.DeepEqual(loaded.CthulhuModifierKeys, values.CthulhuModifierKeys) { t.Fatalf("save keyboard: %v", err) } values.KeyboardLayout = 1 values.CthulhuModifierKeys = []string{"KP_Insert"} rate := 70.0 values.Rate = &rate response = worker.apply(context.Background(), preferences.Request{Values: values}) if response.Accepted || recorder.layout != input.LaptopKeyboardLayout || !reflect.DeepEqual(recorder.modifiers, []string{"Caps_Lock"}) { t.Fatal("later backend failure did not restore keyboard configuration") } values.Rate = nil values.CthulhuModifierKeys = []string{} response = worker.apply(context.Background(), preferences.Request{Values: values, Save: true}) if !response.Accepted || len(recorder.modifiers) != 0 { t.Fatalf("all unchecked: %+v", response) } loaded, err = config.Load(settings.Path) if err != nil || len(loaded.CthulhuModifierKeys) != 0 { t.Fatalf("reload all unchecked: %v", err) } }