Wire the Core pieces into a runnable tray app with per-game profiles and the three-state serial-yield auto-switch: - Profiles/: RioProfile + AppConfig model; ConfigStore (System.Text.Json, round-tripped); RioIniImporter ports the legacy RIO.ini (button table, invert flags, plasma greeting); AutoSwitchResolver + AutoSwitchWatcher resolve the foreground executable into Yield (native game) / Activate (profile) / Idle, with native always winning and change-only notifications. IForegroundProcessProvider abstracts the OS. - RioRuntime assembles a profile's live pipeline: serial ButtonPressed/Released + KeyPressed/Released → InputRouter (via RioAddress); AnalogReply → AxisCalibrator → the six joystick axes; RIO commands → calibration resets + version/check requests + lamp re-init. SerialLampSink sends lamp feedback over the link; NullJoystickSink is a placeholder until the Phase 1 HID feeder exists. - RioJoy.Tray: NotifyIcon menu mirroring the legacy console menu (axis resets, version/status, raw-axes & poll-rate toggles, quit) + profile selection (auto vs. manual) + "start with Windows"; RioCoordinator owns the serial acquire/release tied to the watcher (native-game COM-port yield). OS adapters: ForegroundProcessProvider (Win32 foreground PID→exe) and AutoStartManager (HKCU Run key). - tests: 18 new xUnit tests (123 total) for config round-trip, ini import, the three-state resolver + watcher, and RioRuntime end-to-end over the fake transport (button→joystick, keypad-offset→keyboard, analog→six axes). The joystick output stays a no-op until the Phase 1 driver; on-cabinet verification of the acquire/release lifecycle remains. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
78 lines
2.5 KiB
C#
78 lines
2.5 KiB
C#
using RioJoy.Core.Calibration;
|
|
using RioJoy.Core.Profiles;
|
|
using Xunit;
|
|
|
|
namespace RioJoy.Core.Tests.Profiles;
|
|
|
|
public class ConfigStoreTests
|
|
{
|
|
[Fact]
|
|
public void RoundTrips_FullConfig()
|
|
{
|
|
var config = new AppConfig
|
|
{
|
|
DefaultRioComPort = "COM3",
|
|
DefaultPlasmaComPort = "COM4",
|
|
NativeGameExecutables = { "firestorm.exe", "redplanet.exe" },
|
|
NeutralProfileName = "Desktop",
|
|
AutoStart = true,
|
|
Profiles =
|
|
{
|
|
new RioProfile
|
|
{
|
|
Name = "Doom",
|
|
RioComPort = "COM5",
|
|
PlasmaGreeting = "DOOM",
|
|
Buttons = { [0] = 0x8049, [0x50] = 0x1009 },
|
|
Calibration = new AxisCalibrationConfig { InvertY = true, EnableZR = false },
|
|
MatchExecutables = { "doom.exe" },
|
|
},
|
|
},
|
|
};
|
|
|
|
string json = ConfigStore.Serialize(config);
|
|
AppConfig back = ConfigStore.Deserialize(json);
|
|
|
|
Assert.Equal("COM3", back.DefaultRioComPort);
|
|
Assert.Equal(new[] { "firestorm.exe", "redplanet.exe" }, back.NativeGameExecutables);
|
|
Assert.True(back.AutoStart);
|
|
|
|
RioProfile p = Assert.Single(back.Profiles);
|
|
Assert.Equal("Doom", p.Name);
|
|
Assert.Equal("COM5", p.RioComPort);
|
|
Assert.Equal(0x8049, p.Buttons[0]);
|
|
Assert.Equal(0x1009, p.Buttons[0x50]);
|
|
Assert.True(p.Calibration.InvertY);
|
|
Assert.False(p.Calibration.EnableZR);
|
|
Assert.Equal(new[] { "doom.exe" }, p.MatchExecutables);
|
|
}
|
|
|
|
[Fact]
|
|
public void Load_MissingFile_ReturnsDefaults()
|
|
{
|
|
string path = Path.Combine(Path.GetTempPath(), $"riojoy-missing-{Guid.NewGuid():N}.json");
|
|
AppConfig config = ConfigStore.Load(path);
|
|
Assert.Empty(config.Profiles);
|
|
Assert.Equal("COM1", config.DefaultRioComPort);
|
|
}
|
|
|
|
[Fact]
|
|
public void Save_ThenLoad_File()
|
|
{
|
|
string path = Path.Combine(Path.GetTempPath(), $"riojoy-{Guid.NewGuid():N}", "config.json");
|
|
try
|
|
{
|
|
var config = new AppConfig { DefaultRioComPort = "COM9" };
|
|
ConfigStore.Save(config, path);
|
|
Assert.True(File.Exists(path));
|
|
Assert.Equal("COM9", ConfigStore.Load(path).DefaultRioComPort);
|
|
}
|
|
finally
|
|
{
|
|
string? dir = Path.GetDirectoryName(path);
|
|
if (dir is not null && Directory.Exists(dir))
|
|
Directory.Delete(dir, recursive: true);
|
|
}
|
|
}
|
|
}
|