Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions Pointframe.Tests/AppTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,21 @@ namespace Pointframe.Tests;
public sealed class AppTests
{
[Fact]
public void ConfigureServices_RegistersCoreServicesAndFactories()
public void AddPointframeAppServices_RegistersCoreServicesAndFactories()
{
var services = new ServiceCollection();
// The production host registers logging and configuration; mirror that here.
services.AddLogging();
services.AddSingleton<Microsoft.Extensions.Configuration.IConfiguration>(
new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build());

typeof(App)
.GetMethod("ConfigureServices", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!
.Invoke(null, [services]);
services.AddPointframeAppServices();

using var provider = services.BuildServiceProvider();

Assert.IsType<DialogService>(provider.GetRequiredService<IDialogService>());
Assert.IsType<MessageBoxService>(provider.GetRequiredService<IMessageBoxService>());
Assert.IsType<TrayIconManager>(provider.GetRequiredService<ITrayIconManager>());
Assert.NotNull(provider.GetRequiredService<Func<IScreenRecordingService, string, RecordingHudViewModel>>());
}

Expand Down
61 changes: 61 additions & 0 deletions Pointframe.Tests/Models/HotkeyBindingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.Windows.Input;
using Pointframe.Models;
using Xunit;

namespace Pointframe.Tests.Models;

public sealed class HotkeyBindingTests
{
[Fact]
public void DisplayName_WhenKeyNotSet_ReturnsNotSet()
{
Assert.Equal("Not set", new HotkeyBinding(0, HotkeyModifiers.Ctrl).DisplayName);
}

[Fact]
public void DisplayName_ComposesModifiersInCtrlShiftAltOrder()
{
var binding = new HotkeyBinding(0x53, HotkeyModifiers.Ctrl | HotkeyModifiers.Shift | HotkeyModifiers.Alt); // S

Assert.Equal("Ctrl+Shift+Alt+S", binding.DisplayName);
}

[Fact]
public void DisplayName_ForPrintScreen_UsesFriendlyName()
{
Assert.Equal("Print Screen", new HotkeyBinding(0x2C, HotkeyModifiers.None).DisplayName);
}

[Fact]
public void Matches_WhenKeyAndModifiersMatch_ReturnsTrue()
{
var binding = new HotkeyBinding(0x53, HotkeyModifiers.Ctrl); // S

Assert.True(binding.Matches(Key.S, ModifierKeys.Control));
}

[Fact]
public void Matches_WhenModifiersDiffer_ReturnsFalse()
{
var binding = new HotkeyBinding(0x53, HotkeyModifiers.Ctrl); // S

Assert.False(binding.Matches(Key.S, ModifierKeys.Control | ModifierKeys.Shift));
Assert.False(binding.Matches(Key.S, ModifierKeys.None));
}

[Fact]
public void Matches_WhenKeyDiffers_ReturnsFalse()
{
var binding = new HotkeyBinding(0x53, HotkeyModifiers.Ctrl); // S

Assert.False(binding.Matches(Key.A, ModifierKeys.Control));
}

[Fact]
public void Matches_WhenKeyNotSet_NeverMatches()
{
var binding = new HotkeyBinding(0, HotkeyModifiers.None);

Assert.False(binding.Matches(Key.None, ModifierKeys.None));
}
}
27 changes: 11 additions & 16 deletions Pointframe.Tests/OverlayWindowInteractionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,17 +150,19 @@ public void WindowKeyDown_Escape_WhenTextLassoActive_ClearsLassoState()
try
{
var lassoRect = Assert.IsType<Rectangle>(context.Window.FindName("OcrLassoRect"));
lassoRect.Visibility = Visibility.Visible;
context.ViewModel.InitializeAnnotatingSession(new Rect(0d, 0d, 100d, 80d), 1d, 1d);
context.ViewModel.IsTextLassoActive = true;
SetPrivateField(context.Window, "_lassoStart", new Point(12d, 14d));
var lasso = GetPrivateField<OcrLassoController>(context.Window, "_ocrLasso");
lasso.HandlePointerDown(new Point(12d, 14d));
Assert.Equal(Visibility.Visible, lassoRect.Visibility);
Assert.True(lasso.HasPendingLasso);

var args = CreateKeyArgs(Key.Escape);
InvokePrivate(context.Window, "Window_KeyDown", context.Window, args);

Assert.False(context.ViewModel.IsTextLassoActive);
Assert.Equal(Visibility.Collapsed, lassoRect.Visibility);
Assert.Null(GetPrivateField<Point?>(context.Window, "_lassoStart"));
Assert.False(lasso.HasPendingLasso);
Assert.True(args.Handled);
}
finally
Expand Down Expand Up @@ -344,8 +346,8 @@ public void DoLassoOcr_WhenBackgroundIsMissing_DoesNotInvokeOcrService()
var context = CreateContext();
try
{
var task = Assert.IsAssignableFrom<Task>(InvokePrivate(context.Window, "DoLassoOcr", new Rect(1d, 2d, 30d, 16d)));
task.GetAwaiter().GetResult();
var lasso = GetPrivateField<OcrLassoController>(context.Window, "_ocrLasso");
lasso.RecognizeAsync(new Rect(1d, 2d, 30d, 16d)).GetAwaiter().GetResult();

context.OcrServiceMock.Verify(service => service.Recognize(It.IsAny<BitmapSource>()), Times.Never);
}
Expand All @@ -369,8 +371,8 @@ public void DoLassoOcr_WhenNoTextDetected_TracksAttemptAndNoTextTelemetry()
.Setup(service => service.Recognize(It.IsAny<BitmapSource>()))
.ReturnsAsync(" ");

var task = Assert.IsAssignableFrom<Task>(InvokePrivate(context.Window, "DoLassoOcr", new Rect(1d, 2d, 10d, 6d)));
task.GetAwaiter().GetResult();
var lasso = GetPrivateField<OcrLassoController>(context.Window, "_ocrLasso");
lasso.RecognizeAsync(new Rect(1d, 2d, 10d, 6d)).GetAwaiter().GetResult();

context.TelemetryMock.Verify(
telemetry => telemetry.TrackEvent(
Expand Down Expand Up @@ -412,8 +414,8 @@ public void DoLassoOcr_WhenTextDetected_TracksAttemptAndUsedTelemetry()
.Setup(service => service.Recognize(It.IsAny<BitmapSource>()))
.ReturnsAsync("copied text");

var task = Assert.IsAssignableFrom<Task>(InvokePrivate(context.Window, "DoLassoOcr", new Rect(2d, 3d, 8d, 5d)));
task.GetAwaiter().GetResult();
var lasso = GetPrivateField<OcrLassoController>(context.Window, "_ocrLasso");
lasso.RecognizeAsync(new Rect(2d, 3d, 8d, 5d)).GetAwaiter().GetResult();

context.TelemetryMock.Verify(
telemetry => telemetry.TrackEvent(
Expand Down Expand Up @@ -562,13 +564,6 @@ private static T GetPrivateField<T>(object target, string fieldName)
return (T)field.GetValue(target)!;
}

private static void SetPrivateField<T>(object target, string fieldName, T value)
{
var field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(field);
field.SetValue(target, value);
}

private static KeyEventArgs CreateKeyArgs(Key key)
{
var source = new HwndSource(new HwndSourceParameters("OverlayWindowInteractionTests")
Expand Down
84 changes: 84 additions & 0 deletions Pointframe.Tests/Services/RecordingMicrophoneSessionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Pointframe.Services;
using Xunit;

namespace Pointframe.Tests.Services;

public sealed class RecordingMicrophoneSessionTests
{
[Fact]
public void Constructor_WithoutDevice_IsDisabledAndNotToggleable()
{
var session = new RecordingMicrophoneSession(
Mock.Of<IMicrophoneDeviceService>(),
NullLogger.Instance,
deviceName: null);

Assert.False(session.IsEnabled);
Assert.False(session.CanToggleMute);
Assert.False(session.InitialMutedState);
}

[Fact]
public void Constructor_WithDevice_CapturesInitialMuteState()
{
var microphoneService = Mock.Of<IMicrophoneDeviceService>(service =>
service.TryGetCaptureDeviceMuted("Studio Mic") == true);

var session = new RecordingMicrophoneSession(microphoneService, NullLogger.Instance, "Studio Mic");

Assert.True(session.IsEnabled);
Assert.True(session.CanToggleMute);
Assert.True(session.InitialMutedState);
}

[Fact]
public void TrySetMuted_WithoutDevice_ReturnsFalse()
{
var session = new RecordingMicrophoneSession(
Mock.Of<IMicrophoneDeviceService>(),
NullLogger.Instance,
deviceName: null);

Assert.False(session.TrySetMuted(true));
}

[Fact]
public void TrySetMuted_WhenDeviceCallFails_ReturnsFalse()
{
var microphoneService = new Mock<IMicrophoneDeviceService>();
microphoneService.Setup(service => service.TryGetCaptureDeviceMuted("Studio Mic")).Returns(false);
microphoneService.Setup(service => service.TrySetCaptureDeviceMuted("Studio Mic", true)).Returns(false);

var session = new RecordingMicrophoneSession(microphoneService.Object, NullLogger.Instance, "Studio Mic");

Assert.False(session.TrySetMuted(true));
}

[Fact]
public void RestoreInitialMuteState_RestoresCapturedState()
{
var microphoneService = new Mock<IMicrophoneDeviceService>();
microphoneService.Setup(service => service.TryGetCaptureDeviceMuted("Studio Mic")).Returns(false);
microphoneService.Setup(service => service.TrySetCaptureDeviceMuted("Studio Mic", It.IsAny<bool>())).Returns(true);

var session = new RecordingMicrophoneSession(microphoneService.Object, NullLogger.Instance, "Studio Mic");
session.TrySetMuted(true);
session.RestoreInitialMuteState();

microphoneService.Verify(service => service.TrySetCaptureDeviceMuted("Studio Mic", false), Times.Once);
}

[Fact]
public void RestoreInitialMuteState_WhenInitialStateUnknown_DoesNothing()
{
var microphoneService = new Mock<IMicrophoneDeviceService>();
microphoneService.Setup(service => service.TryGetCaptureDeviceMuted("Studio Mic")).Returns((bool?)null);

var session = new RecordingMicrophoneSession(microphoneService.Object, NullLogger.Instance, "Studio Mic");
session.RestoreInitialMuteState();

microphoneService.Verify(service => service.TrySetCaptureDeviceMuted(It.IsAny<string>(), It.IsAny<bool>()), Times.Never);
}
}
Loading
Loading