acdream/tests/AcDream.App.Tests/Input/DispatcherMovementInputSourceTests.cs
Erik 8b8afeefa3 refactor(input): own pointer and callback lifetime
Move camera pointer, framebuffer resize, and retained/devtools input edges behind focused reversible owners. Preserve input priority while making shutdown deactivate callbacks before live-session retirement and retry physical detach without stranding transport teardown.
2026-07-22 11:59:33 +02:00

174 lines
5.6 KiB
C#

using AcDream.App.Input;
using AcDream.UI.Abstractions.Input;
using Silk.NET.Input;
namespace AcDream.App.Tests.Input;
public sealed class DispatcherMovementInputSourceTests
{
[Fact]
public void UnboundSourceCapturesNeutralInput()
{
var source = new DispatcherMovementInputSource();
Assert.Equal(default, source.Capture());
}
[Fact]
public void CapturesDispatcherHeldStateAndRetailWalkModifier()
{
var (dispatcher, _, _) = CreateDispatcher();
var source = new DispatcherMovementInputSource();
source.Bind(dispatcher);
Assert.True(dispatcher.TrySetAutomationActionHeld(
InputAction.MovementForward,
held: true));
Assert.True(dispatcher.TrySetAutomationActionHeld(
InputAction.MovementWalkMode,
held: true));
MovementInput captured = source.Capture();
Assert.True(captured.Forward);
Assert.False(captured.Run);
}
[Fact]
public void RetainedKeyboardCaptureSilencesHeldKeysButDoesNotCancelAutorun()
{
var (dispatcher, _, mouse) = CreateDispatcher();
var source = new DispatcherMovementInputSource();
source.Bind(dispatcher);
dispatcher.TrySetAutomationActionHeld(InputAction.MovementForward, held: true);
Assert.True(source.HandlePressedAction(InputAction.MovementRunLock));
mouse.WantCaptureKeyboard = true;
MovementInput captured = source.Capture();
Assert.True(captured.Forward);
Assert.True(source.AutoRunActive);
source.HandlePressedAction(InputAction.MovementRunLock);
captured = source.Capture();
Assert.False(captured.Forward);
}
[Fact]
public void DevToolsKeyboardCapturePausesAutorunWithoutClearingItsLatch()
{
var capture = new FakeCapture();
var (dispatcher, _, _) = CreateDispatcher();
var source = new DispatcherMovementInputSource(capture);
source.Bind(dispatcher);
source.HandlePressedAction(InputAction.MovementRunLock);
capture.DevToolsWantCaptureKeyboard = true;
Assert.Equal(default, source.Capture());
Assert.True(source.AutoRunActive);
capture.DevToolsWantCaptureKeyboard = false;
Assert.True(source.Capture().Forward);
}
[Theory]
[InlineData(InputAction.MovementBackup)]
[InlineData(InputAction.MovementStop)]
[InlineData(InputAction.MovementStrafeLeft)]
[InlineData(InputAction.MovementStrafeRight)]
public void RetailCancelActionsClearAutorun(InputAction action)
{
var source = new DispatcherMovementInputSource();
source.HandlePressedAction(InputAction.MovementRunLock);
Assert.False(source.HandlePressedAction(action));
Assert.False(source.AutoRunActive);
}
[Fact]
public void ForwardDoesNotCancelAutorunAndResetDoes()
{
var source = new DispatcherMovementInputSource();
source.HandlePressedAction(InputAction.MovementRunLock);
Assert.False(source.HandlePressedAction(InputAction.MovementForward));
Assert.True(source.AutoRunActive);
source.ResetSession();
Assert.False(source.AutoRunActive);
}
[Fact]
public void BindingIsIdempotentOnlyForTheSameDispatcher()
{
var source = new DispatcherMovementInputSource();
var (first, _, _) = CreateDispatcher();
var (second, _, _) = CreateDispatcher();
source.Bind(first);
source.Bind(first);
Assert.Throws<InvalidOperationException>(() => source.Bind(second));
}
[Fact]
public void UnbindRequiresExactDispatcherAndRestoresNeutralCapture()
{
var source = new DispatcherMovementInputSource();
var (first, _, _) = CreateDispatcher();
var (other, _, _) = CreateDispatcher();
source.Bind(first);
first.TrySetAutomationActionHeld(InputAction.MovementForward, held: true);
source.Unbind(other);
Assert.True(source.Capture().Forward);
source.Unbind(first);
Assert.False(source.IsAvailable);
Assert.Equal(default, source.Capture());
}
private static (InputDispatcher Dispatcher, FakeKeyboard Keyboard, FakeMouse Mouse)
CreateDispatcher()
{
var keyboard = new FakeKeyboard();
var mouse = new FakeMouse();
var dispatcher = InputDispatcher.CreateDetached(
keyboard,
mouse,
new KeyBindings());
dispatcher.Attach();
return (dispatcher, keyboard, mouse);
}
private sealed class FakeKeyboard : IKeyboardSource
{
#pragma warning disable CS0067
public event Action<Key, ModifierMask>? KeyDown;
public event Action<Key, ModifierMask>? KeyUp;
#pragma warning restore CS0067
public bool IsHeld(Key key) => false;
public ModifierMask CurrentModifiers => ModifierMask.None;
}
private sealed class FakeMouse : IMouseSource
{
#pragma warning disable CS0067
public event Action<MouseButton, ModifierMask>? MouseDown;
public event Action<MouseButton, ModifierMask>? MouseUp;
public event Action<float, float>? MouseMove;
public event Action<float>? Scroll;
#pragma warning restore CS0067
public bool WantCaptureKeyboard { get; set; }
public bool WantCaptureMouse { get; set; }
public bool IsHeld(MouseButton button) => false;
}
private sealed class FakeCapture : IInputCaptureSource
{
public bool WantCaptureMouse { get; set; }
public bool WantCaptureKeyboard { get; set; }
public bool DevToolsWantCaptureKeyboard { get; set; }
}
}