using System.IO; using AcDream.UI.Abstractions.Panels.Settings; namespace AcDream.UI.Abstractions.Tests.Panels.Settings; /// /// L.0: reads / writes settings.json. /// Tests use a temp-file path so they don't touch the user's /// %LOCALAPPDATA% file. /// public sealed class SettingsStoreTests : System.IDisposable { private readonly string _tempPath; public SettingsStoreTests() { // Unique per-test file under the system temp dir so parallel test // runners don't clobber each other. _tempPath = Path.Combine( Path.GetTempPath(), $"acdream-settings-test-{System.Guid.NewGuid():N}.json"); } public void Dispose() { if (File.Exists(_tempPath)) File.Delete(_tempPath); } [Fact] public void LoadDisplay_returns_defaults_when_file_is_missing() { var store = new SettingsStore(_tempPath); var loaded = store.LoadDisplay(); Assert.Equal(DisplaySettings.Default, loaded); } [Fact] public void SaveDisplay_then_LoadDisplay_round_trips_all_fields() { var store = new SettingsStore(_tempPath); var original = new DisplaySettings( Resolution: "2560x1440", Fullscreen: true, VSync: false, FieldOfView: 100f, Gamma: 1.4f, ShowFps: true); store.SaveDisplay(original); var loaded = store.LoadDisplay(); Assert.Equal(original, loaded); } [Fact] public void LoadDisplay_falls_back_to_defaults_when_file_is_corrupt() { File.WriteAllText(_tempPath, "{ this is not valid json"); var store = new SettingsStore(_tempPath); var loaded = store.LoadDisplay(); Assert.Equal(DisplaySettings.Default, loaded); } [Fact] public void LoadDisplay_falls_back_per_field_when_keys_missing() { // Partial file — only resolution set; everything else should // pick up DisplaySettings.Default values. File.WriteAllText(_tempPath, """ { "version": 1, "display": { "resolution": "1366x768" } } """); var store = new SettingsStore(_tempPath); var loaded = store.LoadDisplay(); Assert.Equal("1366x768", loaded.Resolution); Assert.Equal(DisplaySettings.Default.Fullscreen, loaded.Fullscreen); Assert.Equal(DisplaySettings.Default.VSync, loaded.VSync); Assert.Equal(DisplaySettings.Default.FieldOfView, loaded.FieldOfView); } [Fact] public void SaveDisplay_preserves_unknown_top_level_keys() { // Forward-compat: a newer client may have written sections we // don't know about (audio, gameplay). Saving display must not // delete those, otherwise running an older client would silently // drop the user's other-tab preferences. File.WriteAllText(_tempPath, """ { "version": 1, "display": { "resolution": "1280x720" }, "audio": { "master": 0.5, "music": 0.7 } } """); var store = new SettingsStore(_tempPath); store.SaveDisplay(DisplaySettings.Default with { Resolution = "1920x1080" }); var raw = File.ReadAllText(_tempPath); Assert.Contains("\"audio\"", raw); Assert.Contains("\"master\"", raw); Assert.Contains("0.5", raw); // And the new display value did get written. Assert.Contains("1920x1080", raw); } [Fact] public void DefaultPath_is_under_LocalAppData_acdream() { var path = SettingsStore.DefaultPath(); Assert.EndsWith("acdream" + Path.DirectorySeparatorChar + "settings.json", path); } // -- Audio section round-trip ---------------------------------------- [Fact] public void LoadAudio_returns_defaults_when_file_is_missing() { var store = new SettingsStore(_tempPath); Assert.Equal(AudioSettings.Default, store.LoadAudio()); } [Fact] public void SaveAudio_then_LoadAudio_round_trips_all_fields() { var store = new SettingsStore(_tempPath); var original = new AudioSettings(Master: 0.3f, Music: 0.45f, Sfx: 0.9f, Ambient: 0.6f); store.SaveAudio(original); var loaded = store.LoadAudio(); Assert.Equal(original, loaded); } [Fact] public void LoadAudio_falls_back_per_field_when_keys_missing() { File.WriteAllText(_tempPath, """ { "version": 1, "audio": { "master": 0.25 } } """); var store = new SettingsStore(_tempPath); var loaded = store.LoadAudio(); Assert.Equal(0.25f, loaded.Master); Assert.Equal(AudioSettings.Default.Music, loaded.Music); Assert.Equal(AudioSettings.Default.Sfx, loaded.Sfx); Assert.Equal(AudioSettings.Default.Ambient, loaded.Ambient); } [Fact] public void SaveAudio_preserves_display_section() { // Save display first, then audio — display values must survive. var store = new SettingsStore(_tempPath); store.SaveDisplay(DisplaySettings.Default with { Resolution = "2560x1440" }); store.SaveAudio(AudioSettings.Default with { Master = 0.4f }); Assert.Equal("2560x1440", store.LoadDisplay().Resolution); Assert.Equal(0.4f, store.LoadAudio().Master); } [Fact] public void SaveDisplay_after_SaveAudio_preserves_audio_section() { // Reverse order — audio must survive a subsequent display save. var store = new SettingsStore(_tempPath); store.SaveAudio(AudioSettings.Default with { Music = 0.1f }); store.SaveDisplay(DisplaySettings.Default with { ShowFps = true }); Assert.Equal(0.1f, store.LoadAudio().Music); Assert.True(store.LoadDisplay().ShowFps); } // -- Gameplay section round-trip -------------------------------------- [Fact] public void LoadGameplay_returns_defaults_when_file_is_missing() { var store = new SettingsStore(_tempPath); Assert.Equal(GameplaySettings.Default, store.LoadGameplay()); } [Fact] public void SaveGameplay_then_LoadGameplay_round_trips_all_fields() { var store = new SettingsStore(_tempPath); var original = GameplaySettings.Default with { AutoTarget = false, AdvancedCombatUI = true, ShowHelm = false, LockUI = true, UseMouseTurning = true, }; store.SaveGameplay(original); var loaded = store.LoadGameplay(); Assert.Equal(original, loaded); } [Fact] public void LoadGameplay_falls_back_per_field_when_keys_missing() { File.WriteAllText(_tempPath, """ { "version": 1, "gameplay": { "lockUI": true } } """); var store = new SettingsStore(_tempPath); var loaded = store.LoadGameplay(); Assert.True(loaded.LockUI); Assert.Equal(GameplaySettings.Default.AutoTarget, loaded.AutoTarget); Assert.Equal(GameplaySettings.Default.ShowHelm, loaded.ShowHelm); } [Fact] public void All_three_sections_coexist_in_one_settings_json() { var store = new SettingsStore(_tempPath); store.SaveDisplay(DisplaySettings.Default with { Resolution = "2560x1440" }); store.SaveAudio(AudioSettings.Default with { Master = 0.5f }); store.SaveGameplay(GameplaySettings.Default with { LockUI = true }); // All three load correctly from the same file. Assert.Equal("2560x1440", store.LoadDisplay().Resolution); Assert.Equal(0.5f, store.LoadAudio().Master); Assert.True(store.LoadGameplay().LockUI); } }