Files
meeting-assistant/MeetingAssistant.Tests/WindowsScreenshotImageCropperTests.cs
T
dh 728e66dd72
PR and Push Build/Test / windows-build-and-test (push) Failing after 38m25s
PR and Push Build/Test / portable-build-and-test (push) Successful in 8m9s
PR and Push Build/Test / macos-native-full (push) Skipped
ci: consolidate platform pipelines and test prerequisites on macOS support
2026-10-06 09:02:19 +02:00

110 lines
4.5 KiB
C#

#if WINDOWS
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using MeetingAssistant.Screenshots;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace MeetingAssistant.Tests;
public sealed class WindowsScreenshotImageCropperTests(ITestOutputHelper output)
{
[WindowsFact("System.Drawing screenshot cropping")]
public Task WindowsCropUsesOcrPixelCoordinates() =>
AssertCropAsync(new ScreenshotCropCoordinates(0, 0, 2, 2));
[WindowsFact("System.Drawing screenshot cropping")]
public Task WindowsCropPreservesExpectedPixelsAtNonzeroOrigin() =>
AssertCropAsync(new ScreenshotCropCoordinates(1, 1, 3, 2));
[WindowsFact("System.Drawing screenshot cropping")]
public async Task WindowsCropRejectsInvalidBounds()
{
using var fixture = new ImageFixture();
await using var factory = new IsolatedApplicationFactory();
using var client = factory.CreateClient();
var cropper = GetWindowsCropper(factory);
ScreenshotCropCoordinates[] invalidBounds =
[
new(-1, 0, 1, 1), new(0, -1, 1, 1),
new(0, 0, 0, 1), new(0, 0, 1, 0),
new(0, 0, -1, 1), new(0, 0, 1, -1),
new(4, 0, 1, 1), new(0, 3, 1, 1),
new(3, 0, 2, 1), new(0, 2, 1, 2)
];
foreach (var bounds in invalidBounds)
{
var failure = await Assert.ThrowsAsync<InvalidDataException>(() =>
cropper.SaveCroppedScreenshotAsync(fixture.InputPath, bounds, CancellationToken.None));
Assert.Contains("outside image bounds 4x3", failure.Message);
Assert.False(File.Exists(fixture.CroppedPath));
output.WriteLine($"Rejected {bounds.X},{bounds.Y},{bounds.Width},{bounds.Height}; no output created.");
}
}
private async Task AssertCropAsync(ScreenshotCropCoordinates bounds)
{
using var fixture = new ImageFixture();
await using var factory = new IsolatedApplicationFactory();
using var client = factory.CreateClient();
var cropper = GetWindowsCropper(factory);
var path = await cropper.SaveCroppedScreenshotAsync(
fixture.InputPath, bounds, CancellationToken.None);
Assert.Equal(fixture.CroppedPath, path);
using var cropped = new Bitmap(path!);
Assert.Equal(bounds.Width, cropped.Width);
Assert.Equal(bounds.Height, cropped.Height);
for (var y = 0; y < cropped.Height; y++)
{
for (var x = 0; x < cropped.Width; x++)
{
Assert.Equal(ImageFixture.PixelAt(bounds.X + x, bounds.Y + y).ToArgb(),
cropped.GetPixel(x, y).ToArgb());
}
}
output.WriteLine($"Verified every pixel of {bounds.Width}x{bounds.Height} at original origin {bounds.X},{bounds.Y}.");
}
private IScreenshotImageCropper GetWindowsCropper(IsolatedApplicationFactory factory)
{
var cropper = factory.Services.GetRequiredService<IScreenshotImageCropper>();
Assert.IsType<WindowsScreenshotImageCropper>(cropper);
output.WriteLine($"Registered cropper: {cropper.GetType().Name}; host: {RuntimeInformation.OSDescription}; " +
$"architecture: {RuntimeInformation.ProcessArchitecture}; runtime: {RuntimeInformation.FrameworkDescription}; " +
"target: net10.0-windows10.0.19041.0.");
return cropper;
}
private sealed class ImageFixture : IDisposable
{
private readonly string root = Path.Combine(Path.GetTempPath(), $"meeting-assistant-windows-crop-{Guid.NewGuid():N}");
public ImageFixture()
{
Directory.CreateDirectory(root);
using var image = new Bitmap(4, 3, PixelFormat.Format32bppArgb);
for (var y = 0; y < image.Height; y++)
{
for (var x = 0; x < image.Width; x++)
{
image.SetPixel(x, y, PixelAt(x, y));
}
}
image.Save(InputPath, ImageFormat.Png);
}
public string InputPath => Path.Combine(root, "input.png");
public string CroppedPath => Path.Combine(root, "input-cropped.png");
// Each original coordinate has a distinct opaque color; no resampling tolerance hides a shifted origin.
public static Color PixelAt(int x, int y) => Color.FromArgb(255, 30 + x * 50, 20 + y * 70, 10 + x * 15 + y * 5);
public void Dispose() => Directory.Delete(root, recursive: true);
}
}
#endif