Skip to content

Commit c967554

Browse files
authored
Merge pull request #237 from TeamWheelWizard/dev
Dev
2 parents 2501e73 + f58dc3c commit c967554

File tree

179 files changed

+6605
-1192
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

179 files changed

+6605
-1192
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,9 @@ FodyWeavers.xsd
393393
# Local History for Visual Studio Code
394394
.history/
395395

396+
# Avalonia build task artifacts
397+
**/.avalonia-build-tasks/
398+
396399
# Windows Installer files from build outputs
397400
*.cab
398401
*.msi
@@ -482,3 +485,4 @@ $RECYCLE.BIN/
482485

483486
# Vim temporary swap files
484487
*.swp
488+
AGENTS.md

Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
</provides>
5656

5757
<releases>
58+
<release version="2.4.1" date="2026-02-25"/>
5859
<release version="2.4.0" date="2026-02-17"/>
5960
<release version="2.3.5" date="2026-01-11"/>
6061
<release version="2.3.4" date="2026-01-11"/>
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
using WheelWizard.DolphinInstaller;
2+
using WheelWizard.Shared;
3+
4+
namespace WheelWizard.Test.Features;
5+
6+
public class LinuxDolphinInstallerTests
7+
{
8+
private readonly ILinuxCommandEnvironment _commandEnvironment;
9+
private readonly ILinuxProcessService _processService;
10+
private readonly LinuxDolphinInstaller _installer;
11+
12+
public LinuxDolphinInstallerTests()
13+
{
14+
_commandEnvironment = Substitute.For<ILinuxCommandEnvironment>();
15+
_processService = Substitute.For<ILinuxProcessService>();
16+
_installer = new LinuxDolphinInstaller(_commandEnvironment, _processService);
17+
}
18+
19+
[Fact]
20+
public void IsDolphinInstalledInFlatpak_ReturnsTrue_WhenFlatpakInfoExitCodeIsZero()
21+
{
22+
_processService.Run("flatpak", "info org.DolphinEmu.dolphin-emu").Returns(Ok(0));
23+
24+
var result = _installer.IsDolphinInstalledInFlatpak();
25+
26+
Assert.True(result);
27+
}
28+
29+
[Fact]
30+
public void IsDolphinInstalledInFlatpak_ReturnsFalse_WhenFlatpakInfoExitCodeIsNonZero()
31+
{
32+
_processService.Run("flatpak", "info org.DolphinEmu.dolphin-emu").Returns(Ok(1));
33+
34+
var result = _installer.IsDolphinInstalledInFlatpak();
35+
36+
Assert.False(result);
37+
}
38+
39+
[Fact]
40+
public async Task InstallFlatpak_ReturnsFailure_WhenPackageManagerCannotBeDetected()
41+
{
42+
_commandEnvironment.IsCommandAvailable("flatpak").Returns(false);
43+
_commandEnvironment.DetectPackageManagerInstallCommand().Returns(string.Empty);
44+
45+
var result = await _installer.InstallFlatpak();
46+
47+
Assert.True(result.IsFailure);
48+
Assert.Contains("Unsupported Linux distribution", result.Error.Message);
49+
}
50+
51+
[Fact]
52+
public async Task InstallFlatpak_ReturnsFailure_WhenPkexecIsUnauthorized()
53+
{
54+
_commandEnvironment.IsCommandAvailable("flatpak").Returns(false);
55+
_commandEnvironment.DetectPackageManagerInstallCommand().Returns("apt-get install -y");
56+
_processService
57+
.RunWithProgressAsync("pkexec", "apt-get install -y flatpak", Arg.Any<IProgress<int>?>())
58+
.Returns(Task.FromResult<OperationResult<int>>(Ok(126)));
59+
60+
var result = await _installer.InstallFlatpak();
61+
62+
Assert.True(result.IsFailure);
63+
Assert.Contains("administrator", result.Error.Message);
64+
}
65+
66+
[Fact]
67+
public async Task InstallFlatpak_ReturnsSuccess_WhenInstallCompletesAndCommandBecomesAvailable()
68+
{
69+
_commandEnvironment.IsCommandAvailable("flatpak").Returns(false, true);
70+
_commandEnvironment.DetectPackageManagerInstallCommand().Returns("apt-get install -y");
71+
_processService
72+
.RunWithProgressAsync("pkexec", "apt-get install -y flatpak", Arg.Any<IProgress<int>?>())
73+
.Returns(Task.FromResult<OperationResult<int>>(Ok(0)));
74+
75+
var result = await _installer.InstallFlatpak();
76+
77+
Assert.True(result.IsSuccess);
78+
}
79+
80+
[Fact]
81+
public async Task InstallFlatpakDolphin_ReturnsFailure_WhenDolphinInstallCommandFails()
82+
{
83+
_commandEnvironment.IsCommandAvailable("flatpak").Returns(true);
84+
_processService
85+
.RunWithProgressAsync("pkexec", "flatpak --system install -y org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
86+
.Returns(Task.FromResult<OperationResult<int>>(Ok(1)));
87+
88+
var result = await _installer.InstallFlatpakDolphin();
89+
90+
Assert.True(result.IsFailure);
91+
Assert.Contains("exit code 1", result.Error.Message);
92+
}
93+
94+
[Fact]
95+
public async Task InstallFlatpakDolphin_ReturnsFailure_WhenWarmupLaunchFails()
96+
{
97+
_commandEnvironment.IsCommandAvailable("flatpak").Returns(true);
98+
_processService
99+
.RunWithProgressAsync("pkexec", "flatpak --system install -y org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
100+
.Returns(Task.FromResult<OperationResult<int>>(Ok(0)));
101+
_processService
102+
.LaunchAndStopAsync("flatpak", "run org.DolphinEmu.dolphin-emu", TimeSpan.FromSeconds(4))
103+
.Returns(Task.FromResult<OperationResult>(Fail("Launch failed")));
104+
105+
var result = await _installer.InstallFlatpakDolphin();
106+
107+
Assert.True(result.IsFailure);
108+
Assert.Equal("Launch failed", result.Error.Message);
109+
}
110+
111+
[Fact]
112+
public async Task InstallFlatpakDolphin_ReturnsSuccess_WhenInstallAndWarmupSucceed()
113+
{
114+
_commandEnvironment.IsCommandAvailable("flatpak").Returns(true);
115+
_processService
116+
.RunWithProgressAsync("pkexec", "flatpak --system install -y org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
117+
.Returns(Task.FromResult<OperationResult<int>>(Ok(0)));
118+
_processService
119+
.LaunchAndStopAsync("flatpak", "run org.DolphinEmu.dolphin-emu", TimeSpan.FromSeconds(4))
120+
.Returns(Task.FromResult<OperationResult>(Ok()));
121+
122+
var result = await _installer.InstallFlatpakDolphin();
123+
124+
Assert.True(result.IsSuccess);
125+
}
126+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
using Testably.Abstractions.Testing;
2+
using WheelWizard.Services;
3+
using WheelWizard.Settings;
4+
using WheelWizard.Settings.Types;
5+
6+
namespace WheelWizard.Test.Features.Settings;
7+
8+
[Collection("SettingsFeature")]
9+
public class DolphinSettingTests
10+
{
11+
[Fact]
12+
public void Constructor_Throws_WhenFileNameIsNotIni()
13+
{
14+
var action = () => new DolphinSetting(typeof(string), ("Dolphin.cfg", "General", "NANDRootPath"), "value");
15+
16+
Assert.Throws<ArgumentException>(action);
17+
}
18+
19+
[Fact]
20+
public void SetFromString_ParsesEnumAndFormatsAsIntegerString()
21+
{
22+
var setting = new DolphinSetting(
23+
typeof(DolphinShaderCompilationMode),
24+
("GFX.ini", "Settings", "ShaderCompilationMode"),
25+
DolphinShaderCompilationMode.Default
26+
);
27+
28+
var result = setting.SetFromString("2", skipSave: true);
29+
30+
Assert.True(result);
31+
Assert.Equal(DolphinShaderCompilationMode.HybridUberShaders, Assert.IsType<DolphinShaderCompilationMode>(setting.Get()));
32+
Assert.Equal("2", setting.GetStringValue());
33+
}
34+
35+
[Fact]
36+
public void Set_ReturnsFalseAndKeepsOldValue_WhenValidationFails()
37+
{
38+
var setting = new DolphinSetting(typeof(int), ("GFX.ini", "Settings", "InternalResolution"), 1).SetValidation(value =>
39+
(int)value! >= 0
40+
);
41+
setting.Set(2);
42+
43+
var result = setting.Set(-1);
44+
45+
Assert.False(result);
46+
Assert.Equal(2, Assert.IsType<int>(setting.Get()));
47+
}
48+
49+
[Fact]
50+
public void SetFromString_Throws_WhenTypeIsUnsupported()
51+
{
52+
var setting = new DolphinSetting(typeof(decimal), ("GFX.ini", "Settings", "Price"), 1m);
53+
54+
Assert.Throws<InvalidOperationException>(() => setting.SetFromString("3.14"));
55+
}
56+
}
57+
58+
[Collection("SettingsFeature")]
59+
public class DolphinSettingManagerTests : IDisposable
60+
{
61+
[Fact]
62+
public void LoadSettings_ReadsExistingValue_FromIniFile()
63+
{
64+
var fileSystem = new MockFileSystem();
65+
var userFolderPath = $"/wheelwizard-user-{Guid.NewGuid():N}";
66+
SettingsTestUtils.InitializeSettingsRuntime(userFolderPath);
67+
var configFolderPath = PathManager.ConfigFolderPath;
68+
var iniPath = Path.Combine(configFolderPath, "Dolphin.ini");
69+
fileSystem.Directory.CreateDirectory(configFolderPath);
70+
fileSystem.File.WriteAllLines(iniPath, ["[General]", "NANDRootPath = /persisted"]);
71+
var manager = new DolphinSettingManager(fileSystem);
72+
var setting = new DolphinSetting(typeof(string), ("Dolphin.ini", "General", "NANDRootPath"), "/default");
73+
74+
manager.RegisterSetting(setting);
75+
manager.LoadSettings();
76+
77+
Assert.Equal("/persisted", Assert.IsType<string>(setting.Get()));
78+
}
79+
80+
[Fact]
81+
public void LoadSettings_WritesDefaultValue_WhenIniEntryIsMissing()
82+
{
83+
var fileSystem = new MockFileSystem();
84+
var userFolderPath = $"/wheelwizard-user-{Guid.NewGuid():N}";
85+
SettingsTestUtils.InitializeSettingsRuntime(userFolderPath);
86+
var configFolderPath = PathManager.ConfigFolderPath;
87+
var iniPath = Path.Combine(configFolderPath, "Dolphin.ini");
88+
fileSystem.Directory.CreateDirectory(configFolderPath);
89+
fileSystem.File.WriteAllLines(iniPath, ["[General]", "OtherSetting = 1"]);
90+
var manager = new DolphinSettingManager(fileSystem);
91+
var setting = new DolphinSetting(typeof(string), ("Dolphin.ini", "General", "NANDRootPath"), "/default");
92+
93+
manager.RegisterSetting(setting);
94+
manager.LoadSettings();
95+
96+
var updatedFile = fileSystem.File.ReadAllText(iniPath);
97+
Assert.Contains("NANDRootPath = /default", updatedFile);
98+
}
99+
100+
[Fact]
101+
public void SaveSettings_UpdatesExistingSettingLine_InIniFile()
102+
{
103+
var fileSystem = new MockFileSystem();
104+
var userFolderPath = $"/wheelwizard-user-{Guid.NewGuid():N}";
105+
SettingsTestUtils.InitializeSettingsRuntime(userFolderPath);
106+
var configFolderPath = PathManager.ConfigFolderPath;
107+
var iniPath = Path.Combine(configFolderPath, "Dolphin.ini");
108+
fileSystem.Directory.CreateDirectory(configFolderPath);
109+
fileSystem.File.WriteAllLines(iniPath, ["[General]", "NANDRootPath = /old"]);
110+
var manager = new DolphinSettingManager(fileSystem);
111+
var setting = new DolphinSetting(typeof(string), ("Dolphin.ini", "General", "NANDRootPath"), "/default");
112+
113+
manager.RegisterSetting(setting);
114+
manager.LoadSettings();
115+
setting.Set("/new", skipSave: true);
116+
manager.SaveSettings(setting);
117+
118+
var updatedFile = fileSystem.File.ReadAllText(iniPath);
119+
Assert.Contains("NANDRootPath = /new", updatedFile);
120+
Assert.DoesNotContain("NANDRootPath = /old", updatedFile);
121+
}
122+
123+
[Fact]
124+
public void ReloadSettings_ReReadsFile_AfterItChangesOnDisk()
125+
{
126+
var fileSystem = new MockFileSystem();
127+
var userFolderPath = $"/wheelwizard-user-{Guid.NewGuid():N}";
128+
SettingsTestUtils.InitializeSettingsRuntime(userFolderPath);
129+
var configFolderPath = PathManager.ConfigFolderPath;
130+
var iniPath = Path.Combine(configFolderPath, "Dolphin.ini");
131+
fileSystem.Directory.CreateDirectory(configFolderPath);
132+
fileSystem.File.WriteAllLines(iniPath, ["[General]", "NANDRootPath = /first"]);
133+
var manager = new DolphinSettingManager(fileSystem);
134+
var setting = new DolphinSetting(typeof(string), ("Dolphin.ini", "General", "NANDRootPath"), "/default");
135+
136+
manager.RegisterSetting(setting);
137+
manager.LoadSettings();
138+
fileSystem.File.WriteAllLines(iniPath, ["[General]", "NANDRootPath = /second"]);
139+
manager.ReloadSettings();
140+
141+
Assert.Equal("/second", Assert.IsType<string>(setting.Get()));
142+
}
143+
144+
public void Dispose()
145+
{
146+
SettingsTestUtils.ResetSettingsRuntime();
147+
SettingsTestUtils.ResetSignalRuntime();
148+
}
149+
}

0 commit comments

Comments
 (0)