Fix smoke 2차 gaps: hotkey tests, focus filter, viewport picking (#13)
Gap E — Hotkey named key - UiaPlayerHost: extract ParsedHotkey record + ParseHotkey static - HotkeyParseTests: 8 tests (enter/tab/a/ctrl+c/ctrl+shift+s/f5/alt+f4/empty) Gap F — recorder focus_change SUT filter - FocusEventFilter.ShouldAccept static rule (same/zero/unknown/unknown-sut) - Program.cs wires it inside RegisterFocusChangedEvent callback - FocusEventFilterTests: 4 tests Gap G — viewport picking foreign-process fallback - IWindowPointSource + WindowPointResolver pure resolver - FlaUiPointSource wired in Program.cs (best-effort hit test, honest partial for live SUT) - WindowPointResolverTests: 5 tests Tests: 77 → 94, build 0/0 (TreatWarningsAsErrors preserved). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
71
tests/Recordingtest.Player.Tests/HotkeyParseTests.cs
Normal file
71
tests/Recordingtest.Player.Tests/HotkeyParseTests.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using FlaUI.Core.WindowsAPI;
|
||||
using Xunit;
|
||||
|
||||
namespace Recordingtest.Player.Tests;
|
||||
|
||||
public class HotkeyParseTests
|
||||
{
|
||||
[Fact]
|
||||
public void Enter_NoMods_ReturnsReturnKey()
|
||||
{
|
||||
var p = UiaPlayerHost.ParseHotkey("enter");
|
||||
Assert.Empty(p.Modifiers);
|
||||
Assert.Equal(VirtualKeyShort.RETURN, p.Main);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tab_NoMods_ReturnsTabKey()
|
||||
{
|
||||
var p = UiaPlayerHost.ParseHotkey("tab");
|
||||
Assert.Empty(p.Modifiers);
|
||||
Assert.Equal(VirtualKeyShort.TAB, p.Main);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleChar_a_ReturnsAKey()
|
||||
{
|
||||
var p = UiaPlayerHost.ParseHotkey("a");
|
||||
Assert.Empty(p.Modifiers);
|
||||
Assert.Equal((VirtualKeyShort)'A', p.Main);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CtrlC_ReturnsControlModPlusC()
|
||||
{
|
||||
var p = UiaPlayerHost.ParseHotkey("ctrl+c");
|
||||
Assert.Equal(new[] { VirtualKeyShort.CONTROL }, p.Modifiers);
|
||||
Assert.Equal((VirtualKeyShort)'C', p.Main);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CtrlShiftS_ReturnsBothModsPlusS()
|
||||
{
|
||||
var p = UiaPlayerHost.ParseHotkey("ctrl+shift+s");
|
||||
Assert.Equal(new[] { VirtualKeyShort.CONTROL, VirtualKeyShort.SHIFT }, p.Modifiers);
|
||||
Assert.Equal((VirtualKeyShort)'S', p.Main);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void F5_ReturnsF5Key()
|
||||
{
|
||||
var p = UiaPlayerHost.ParseHotkey("f5");
|
||||
Assert.Empty(p.Modifiers);
|
||||
Assert.Equal((VirtualKeyShort)(0x70 + 4), p.Main);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AltF4_ReturnsAltPlusF4()
|
||||
{
|
||||
var p = UiaPlayerHost.ParseHotkey("alt+f4");
|
||||
Assert.Equal(new[] { VirtualKeyShort.ALT }, p.Modifiers);
|
||||
Assert.Equal((VirtualKeyShort)(0x70 + 3), p.Main);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Empty_ReturnsNoModsNoMain()
|
||||
{
|
||||
var p = UiaPlayerHost.ParseHotkey("");
|
||||
Assert.Empty(p.Modifiers);
|
||||
Assert.Null(p.Main);
|
||||
}
|
||||
}
|
||||
30
tests/Recordingtest.Recorder.Tests/FocusEventFilterTests.cs
Normal file
30
tests/Recordingtest.Recorder.Tests/FocusEventFilterTests.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Recordingtest.Recorder.Tests;
|
||||
|
||||
public class FocusEventFilterTests
|
||||
{
|
||||
[Fact]
|
||||
public void Accept_SamePid_ReturnsTrue()
|
||||
{
|
||||
Assert.True(FocusEventFilter.ShouldAccept(1234, 1234));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accept_DifferentPid_ReturnsFalse()
|
||||
{
|
||||
Assert.False(FocusEventFilter.ShouldAccept(9999, 1234));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accept_UnknownCandidatePid_ReturnsFalse()
|
||||
{
|
||||
Assert.False(FocusEventFilter.ShouldAccept(0, 1234));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accept_UnknownSutPid_ReturnsTrue()
|
||||
{
|
||||
Assert.True(FocusEventFilter.ShouldAccept(9999, 0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Recordingtest.Recorder.Tests;
|
||||
|
||||
public class WindowPointResolverTests
|
||||
{
|
||||
private sealed class FakeSnapshot : IElementSnapshot
|
||||
{
|
||||
public string Tag { get; init; } = "";
|
||||
public string ClassName => Tag;
|
||||
public string? AutomationId => null;
|
||||
public string? Name => Tag;
|
||||
public bool IsPassword => false;
|
||||
public (double Left, double Top, double Width, double Height) BoundingRectangle => (0, 0, 0, 0);
|
||||
public IElementSnapshot? Parent => null;
|
||||
}
|
||||
|
||||
private sealed class FakeSource : IWindowPointSource
|
||||
{
|
||||
public int? Pid { get; set; }
|
||||
public IElementSnapshot? Primary { get; set; }
|
||||
public IElementSnapshot? SutScope { get; set; }
|
||||
public int? GetProcessIdAt(int x, int y) => Pid;
|
||||
public IElementSnapshot? GetElementAt(int x, int y) => Primary;
|
||||
public IElementSnapshot? GetElementFromSutScope(int x, int y) => SutScope;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resolve_SamePid_ReturnsPrimary()
|
||||
{
|
||||
var primary = new FakeSnapshot { Tag = "primary" };
|
||||
var sut = new FakeSnapshot { Tag = "sut" };
|
||||
var src = new FakeSource { Pid = 100, Primary = primary, SutScope = sut };
|
||||
var r = WindowPointResolver.Resolve(src, 10, 20, sutPid: 100);
|
||||
Assert.Same(primary, r);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resolve_DifferentPid_FallsBackToSutScope()
|
||||
{
|
||||
var primary = new FakeSnapshot { Tag = "primary" };
|
||||
var sut = new FakeSnapshot { Tag = "sut" };
|
||||
var src = new FakeSource { Pid = 999, Primary = primary, SutScope = sut };
|
||||
var r = WindowPointResolver.Resolve(src, 10, 20, sutPid: 100);
|
||||
Assert.Same(sut, r);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resolve_UnknownPid_ReturnsPrimary()
|
||||
{
|
||||
var primary = new FakeSnapshot { Tag = "primary" };
|
||||
var src = new FakeSource { Pid = null, Primary = primary, SutScope = null };
|
||||
var r = WindowPointResolver.Resolve(src, 10, 20, sutPid: 100);
|
||||
Assert.Same(primary, r);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resolve_ZeroPid_ReturnsPrimary()
|
||||
{
|
||||
var primary = new FakeSnapshot { Tag = "primary" };
|
||||
var src = new FakeSource { Pid = 0, Primary = primary, SutScope = null };
|
||||
var r = WindowPointResolver.Resolve(src, 10, 20, sutPid: 100);
|
||||
Assert.Same(primary, r);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resolve_DifferentPid_FallbackNull_ReturnsPrimary()
|
||||
{
|
||||
// Documented semantic: if SUT-scope fallback can't find anything,
|
||||
// keep the primary as a last resort rather than dropping the event.
|
||||
var primary = new FakeSnapshot { Tag = "primary" };
|
||||
var src = new FakeSource { Pid = 999, Primary = primary, SutScope = null };
|
||||
var r = WindowPointResolver.Resolve(src, 10, 20, sutPid: 100);
|
||||
Assert.Same(primary, r);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user