package app import ( "context" "log/slog" "path/filepath" "reflect" "strings" "testing" "git.stormux.org/storm/cthulhu/internal/config" "git.stormux.org/storm/cthulhu/internal/preferences" "git.stormux.org/storm/cthulhu/internal/speech" "git.stormux.org/storm/cthulhu/internal/wine" ) // The Wine tab is global-only: a global preferences Apply carries the edited // Wine values through the preferences protocol into the live global Wine // source the manager reads, persists them through TOML, and needs no reader // restart. An application-scoped Apply never changes the governing values. func TestWinePreferencesGlobalApplyGovernsAndPersists(t *testing.T) { path := filepath.Join(t.TempDir(), "settings.toml") global := config.DefaultSettings() global.Path = path global.Wine = config.WineSettings{ExcludedPrograms: []string{"Game.exe"}} store, err := config.NewStore(path) if err != nil { t.Fatal(err) } if err := store.Save(global); err != nil { t.Fatal(err) } worker := newPreferencesWorker(slog.New(slog.DiscardHandler), global, settingsUpdateFunc(func(context.Context, speech.Settings) error { return nil })) worker.live = &liveSettings{} worker.live.Store(global) worker.live.StoreGlobalWine(global.Wine) manager := newWineManager(slog.New(slog.DiscardHandler), worker.live) governing := func() wine.Settings { return manager.SettingsSource() } ctx := context.Background() // An invalid entry is named and blocks the global Apply. if !worker.beginEdit(ctx, "") { t.Fatal("global preferences did not open") } invalid := preferences.FromSettings(worker.editing) invalid.Wine.ExcludedPrograms = []string{"Game.exe", `C:\Games\Sneaky.exe`} response := worker.apply(ctx, preferences.Request{Values: invalid, Save: true}) if response.Accepted || !strings.Contains(response.Message, `C:\Games\Sneaky.exe`) { t.Fatalf("invalid Wine entry was not named and blocked: %+v", response) } if want := (wine.Settings{ExcludedPrograms: []string{"Game.exe"}}); !reflect.DeepEqual(governing(), want) { t.Fatalf("rejected Apply changed governing Wine settings: %+v", governing()) } // A valid global Apply reaches the same manager with no restart and // persists through TOML. values := preferences.FromSettings(worker.editing) values.Wine = config.WineSettings{ControllerOnly: true, ExcludedPrograms: []string{"Game.exe", "New Game.exe", "new game.EXE"}} response = worker.apply(ctx, preferences.Request{Values: values, Save: true}) if !response.Accepted { t.Fatalf("global Wine Apply rejected: %+v", response) } want := wine.Settings{ControllerOnly: true, ExcludedPrograms: []string{"Game.exe", "New Game.exe"}} if !reflect.DeepEqual(governing(), want) { t.Fatalf("governing Wine settings after Apply = %+v, want %+v", governing(), want) } loaded, err := store.Load() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(loaded.Wine, config.WineSettings(want)) { t.Fatalf("persisted Wine settings = %+v, want %+v", loaded.Wine, want) } worker.editingActive = false // An application-scoped Apply has no Wine editing surface. Even a // request carrying different Wine values cannot change the governing // list, the global profile, or the application profile's Wine values. worker.switchApplication(ctx, "Example Editor") if !worker.beginEdit(ctx, "\x00") || worker.editApp != "Example Editor" { t.Fatal("application preferences did not open") } appValues := preferences.FromSettings(worker.editing) appValues.TimeFormat = "24-hour" appValues.Wine = config.WineSettings{ExcludedPrograms: []string{"Sneaky.exe"}} response = worker.apply(ctx, preferences.Request{Values: appValues, Save: true}) if !response.Accepted || worker.settings.TimeFormat != "24-hour" { t.Fatalf("application Apply rejected: %+v", response) } if !reflect.DeepEqual(governing(), want) { t.Fatalf("application Apply changed governing Wine settings: %+v", governing()) } if !reflect.DeepEqual(worker.globalSettings.Wine, config.WineSettings(want)) { t.Fatalf("application Apply changed global Wine settings: %+v", worker.globalSettings.Wine) } if !reflect.DeepEqual(worker.appSettings["Example Editor"].Wine, config.WineSettings(want)) { t.Fatalf("application Apply rewrote Wine values: %+v", worker.appSettings["Example Editor"].Wine) } reloaded, err := store.Load() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(reloaded.Wine, config.WineSettings(want)) { t.Fatalf("application Apply persisted into the global Wine profile: %+v", reloaded.Wine) } }