Profile editor: - Cockpit-style encoder gauges centered over the Upper Middle MFD: Z left, X/Y right, and L/R/Rz arranged as a big "U". - [JoyStick] section surfaced as a vertical "Axis" toggle column (6 inverts + ZR mix), written back to the profile's calibration. - Tighten the panel layout (close gaps, trim empty border columns); colour the Secondary/Screen columns yellow; rename the "Joystick / Hat" board to "Joystick"; add an "Import .ini..." tray entry. - "RIO commands (live)" button group (all RioCommandCodes) fired at the live RIO. Serial-port handling: - Start dormant; only take the COM port for a profiled game (auto-switch) or while editing, so the native games can open the port without clashing. - Editor sessions hold the port but route keyboard/mouse/joystick to no-op sinks (NullInputSink), so button function can be checked without injecting input; RioRuntime.ButtonActivity drives a live cyan press indicator and lamp feedback still applies. - AutoSwitchWatcher.Reset() re-syncs the watcher after an editor session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
110 lines
3.6 KiB
C#
110 lines
3.6 KiB
C#
using RioJoy.Core.Profiles;
|
|
using Xunit;
|
|
|
|
namespace RioJoy.Core.Tests.Profiles;
|
|
|
|
public class AutoSwitchTests
|
|
{
|
|
private static AppConfig Config() => new()
|
|
{
|
|
NativeGameExecutables = { "firestorm.exe", "RedPlanet" },
|
|
Profiles =
|
|
{
|
|
new RioProfile { Name = "Doom", MatchExecutables = { "doom.exe", "doom2" } },
|
|
new RioProfile { Name = "Quake", MatchExecutables = { "quake.exe" } },
|
|
},
|
|
};
|
|
|
|
[Theory]
|
|
[InlineData("firestorm.exe")]
|
|
[InlineData("FIRESTORM.EXE")]
|
|
[InlineData(@"C:\games\Firestorm\firestorm.exe")]
|
|
[InlineData("redplanet.exe")] // configured without extension, matched with one
|
|
public void NativeGame_Yields(string exe)
|
|
{
|
|
Assert.Equal(SwitchMode.Yield, AutoSwitchResolver.Resolve(Config(), exe).Mode);
|
|
}
|
|
|
|
[Fact]
|
|
public void SupportedGame_Activates_ItsProfile()
|
|
{
|
|
SwitchDecision d = AutoSwitchResolver.Resolve(Config(), @"D:\Games\doom.exe");
|
|
Assert.Equal(SwitchMode.Activate, d.Mode);
|
|
Assert.Equal("Doom", d.Profile!.Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void Unknown_IsIdle_WhenNoNeutral()
|
|
{
|
|
Assert.Equal(SwitchMode.Idle, AutoSwitchResolver.Resolve(Config(), "explorer.exe").Mode);
|
|
Assert.Equal(SwitchMode.Idle, AutoSwitchResolver.Resolve(Config(), null).Mode);
|
|
}
|
|
|
|
[Fact]
|
|
public void Unknown_ActivatesNeutral_WhenConfigured()
|
|
{
|
|
AppConfig config = Config();
|
|
config.NeutralProfileName = "Doom";
|
|
|
|
SwitchDecision d = AutoSwitchResolver.Resolve(config, "explorer.exe");
|
|
Assert.Equal(SwitchMode.Activate, d.Mode);
|
|
Assert.Equal("Doom", d.Profile!.Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void Native_Wins_OverProfileMatch()
|
|
{
|
|
AppConfig config = Config();
|
|
// A profile also claims the native exe; native yield must still win.
|
|
config.Profiles[0].MatchExecutables.Add("firestorm.exe");
|
|
Assert.Equal(SwitchMode.Yield, AutoSwitchResolver.Resolve(config, "firestorm.exe").Mode);
|
|
}
|
|
|
|
[Fact]
|
|
public void Watcher_RaisesOnlyOnChange()
|
|
{
|
|
var config = Config();
|
|
string? exe = "doom.exe";
|
|
var provider = new FakeForeground(() => exe);
|
|
var watcher = new AutoSwitchWatcher(provider, () => config);
|
|
|
|
var changes = new List<SwitchDecision>();
|
|
watcher.DecisionChanged += changes.Add;
|
|
|
|
watcher.Poll(); // doom → Activate
|
|
watcher.Poll(); // doom again → no change
|
|
exe = "firestorm.exe";
|
|
watcher.Poll(); // native → Yield (change)
|
|
exe = "firestorm.exe";
|
|
watcher.Poll(); // same → no change
|
|
|
|
Assert.Equal(2, changes.Count);
|
|
Assert.Equal(SwitchMode.Activate, changes[0].Mode);
|
|
Assert.Equal(SwitchMode.Yield, changes[1].Mode);
|
|
}
|
|
|
|
[Fact]
|
|
public void Reset_ReRaises_SameDecision_OnNextPoll()
|
|
{
|
|
var config = Config();
|
|
var provider = new FakeForeground(() => "doom.exe");
|
|
var watcher = new AutoSwitchWatcher(provider, () => config);
|
|
|
|
var changes = new List<SwitchDecision>();
|
|
watcher.DecisionChanged += changes.Add;
|
|
|
|
watcher.Poll(); // doom → Activate (raise)
|
|
watcher.Poll(); // same → no raise
|
|
watcher.Reset(); // forget last decision (e.g. after an editor session)
|
|
watcher.Poll(); // same decision, but re-raised because of Reset
|
|
|
|
Assert.Equal(2, changes.Count);
|
|
Assert.All(changes, c => Assert.Equal(SwitchMode.Activate, c.Mode));
|
|
}
|
|
|
|
private sealed class FakeForeground(Func<string?> get) : IForegroundProcessProvider
|
|
{
|
|
public string? GetForegroundExecutable() => get();
|
|
}
|
|
}
|