From 0f224ec036751ffefd8dae6d95e3efb7385b9b89 Mon Sep 17 00:00:00 2001 From: kadu04t <119273137+kadu04t@users.noreply.github.com> Date: Sun, 19 Jul 2026 07:53:56 -0300 Subject: [PATCH] Gui Settings Null list Entries (#430) --- src/SharpEmu.GUI/GuiSettings.cs | 31 +++++- src/SharpEmu.GUI/SharpEmu.GUI.csproj | 4 + .../GUI/GuiSettingsTests.cs | 100 ++++++++++++++++++ .../SharpEmu.Libs.Tests.csproj | 1 + 4 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 tests/SharpEmu.Libs.Tests/GUI/GuiSettingsTests.cs diff --git a/src/SharpEmu.GUI/GuiSettings.cs b/src/SharpEmu.GUI/GuiSettings.cs index 441eb74f..fae06814 100644 --- a/src/SharpEmu.GUI/GuiSettings.cs +++ b/src/SharpEmu.GUI/GuiSettings.cs @@ -71,7 +71,7 @@ public sealed class GuiSettings if (File.Exists(SettingsPath)) { var json = File.ReadAllText(SettingsPath); - return JsonSerializer.Deserialize(json, SerializerOptions) ?? new GuiSettings(); + return NormalizeFromJson(json); } } catch (Exception) @@ -82,6 +82,35 @@ public sealed class GuiSettings return new GuiSettings(); } + /// + /// Deserializes settings and normalizes null references and null or empty list + /// entries introduced by JSON. Empty scalar strings remain unchanged. + /// + internal static GuiSettings NormalizeFromJson(string json) + { + var settings = JsonSerializer.Deserialize(json, SerializerOptions) ?? new GuiSettings(); + + settings.GameFolders = FilterNullOrEmpty(settings.GameFolders); + settings.ExcludedGames = FilterNullOrEmpty(settings.ExcludedGames); + settings.EnvironmentToggles = FilterNullOrEmpty(settings.EnvironmentToggles); + settings.LogLevel ??= "Info"; + settings.Language ??= "en"; + settings.DiscordClientId ??= "1525606762248540221"; + + return settings; + } + + // JSON can populate non-nullable lists with null references and entries. + private static List FilterNullOrEmpty(List? source) + { + if (source is null) + { + return []; + } + + return source.Where(entry => !string.IsNullOrEmpty(entry)).ToList(); + } + public void Save() { try diff --git a/src/SharpEmu.GUI/SharpEmu.GUI.csproj b/src/SharpEmu.GUI/SharpEmu.GUI.csproj index 8df6ccd6..740bc0b2 100644 --- a/src/SharpEmu.GUI/SharpEmu.GUI.csproj +++ b/src/SharpEmu.GUI/SharpEmu.GUI.csproj @@ -24,6 +24,10 @@ SPDX-License-Identifier: GPL-2.0-or-later + + + + diff --git a/tests/SharpEmu.Libs.Tests/GUI/GuiSettingsTests.cs b/tests/SharpEmu.Libs.Tests/GUI/GuiSettingsTests.cs new file mode 100644 index 00000000..baac02e2 --- /dev/null +++ b/tests/SharpEmu.Libs.Tests/GUI/GuiSettingsTests.cs @@ -0,0 +1,100 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using SharpEmu.GUI; +using Xunit; + +namespace SharpEmu.Libs.Tests.GUI; + +public sealed class GuiSettingsTests +{ + [Fact] + public void NormalizeFromJson_AllPropertiesNull_FallsBackToDefaults() + { + const string json = """ + { + "LogLevel": null, + "GameFolders": null, + "ExcludedGames": null, + "EnvironmentToggles": null, + "Language": null, + "DiscordClientId": null + } + """; + + var settings = GuiSettings.NormalizeFromJson(json); + + Assert.Equal("Info", settings.LogLevel); + Assert.Equal("en", settings.Language); + Assert.Equal("1525606762248540221", settings.DiscordClientId); + Assert.Empty(settings.GameFolders); + Assert.Empty(settings.ExcludedGames); + Assert.Empty(settings.EnvironmentToggles); + } + + [Fact] + public void NormalizeFromJson_ValidValues_ArePreserved() + { + const string json = """ + { + "LogLevel": "Debug", + "GameFolders": ["C:\\Games"], + "ExcludedGames": ["C:\\Games\\skip.bin"], + "EnvironmentToggles": ["SHARPEMU_TRACE"], + "Language": "pt-BR", + "DiscordClientId": "999" + } + """; + + var settings = GuiSettings.NormalizeFromJson(json); + + Assert.Equal("Debug", settings.LogLevel); + Assert.Equal("pt-BR", settings.Language); + Assert.Equal("999", settings.DiscordClientId); + Assert.Equal(["C:\\Games"], settings.GameFolders); + Assert.Equal(["C:\\Games\\skip.bin"], settings.ExcludedGames); + Assert.Equal(["SHARPEMU_TRACE"], settings.EnvironmentToggles); + } + + // An empty Discord client ID intentionally disables Rich Presence. + [Fact] + public void NormalizeFromJson_EmptyDiscordClientId_IsPreservedNotNormalized() + { + const string json = """{ "DiscordClientId": "" }"""; + + var settings = GuiSettings.NormalizeFromJson(json); + + Assert.Equal(string.Empty, settings.DiscordClientId); + } + + [Fact] + public void NormalizeFromJson_NullOrEmptyListEntries_AreFilteredOut() + { + const string json = """ + { + "GameFolders": ["C:\\Games", null, ""], + "ExcludedGames": [null], + "EnvironmentToggles": [null, "SHARPEMU_TRACE", ""] + } + """; + + var settings = GuiSettings.NormalizeFromJson(json); + + Assert.Equal(["C:\\Games"], settings.GameFolders); + Assert.Empty(settings.ExcludedGames); + Assert.Equal(["SHARPEMU_TRACE"], settings.EnvironmentToggles); + } + + [Fact] + public void NormalizeFromJson_EmptyObject_UsesConstructorDefaults() + { + var settings = GuiSettings.NormalizeFromJson("{}"); + + Assert.Equal("Info", settings.LogLevel); + Assert.Equal("en", settings.Language); + Assert.Equal("1525606762248540221", settings.DiscordClientId); + Assert.Empty(settings.GameFolders); + Assert.Empty(settings.ExcludedGames); + Assert.Empty(settings.EnvironmentToggles); + } +} diff --git a/tests/SharpEmu.Libs.Tests/SharpEmu.Libs.Tests.csproj b/tests/SharpEmu.Libs.Tests/SharpEmu.Libs.Tests.csproj index 7f5081af..e31879b8 100644 --- a/tests/SharpEmu.Libs.Tests/SharpEmu.Libs.Tests.csproj +++ b/tests/SharpEmu.Libs.Tests/SharpEmu.Libs.Tests.csproj @@ -14,6 +14,7 @@ SPDX-License-Identifier: GPL-2.0-or-later +