using System.Drawing; using System.Linq; using System.Windows.Controls; using System.Windows.Threading; using Microsoft.Web.WebView2.Wpf; using Xunit; using ytLive.Models; using ytLive.Services; namespace ytLive.Tests; [Collection("RealApp")] public sealed class WebView2ManagerTests { private readonly RealAppHost _app; public WebView2ManagerTests(RealAppHost app) { _app = app; } [Fact] public void WebView2_Control_Size_Is_Canvas_Resolution_Regardless_Of_Source() { _app.Run(() => { var panel = new Canvas(); var source = new Source { Type = SourceType.WebSource, Width = 640, Height = 480, }; using var manager = new WebView2Manager(panel, Dispatcher.CurrentDispatcher); manager.Register(source); var webView = panel.Children.OfType().First(); Assert.Equal(1920.0, webView.Width); Assert.Equal(1080.0, webView.Height); }); } [Fact] public void WebView2_Control_Size_Does_Not_Track_Box_After_Register() { _app.Run(() => { var panel = new Canvas(); var source = new Source { Type = SourceType.WebSource, Width = 640, Height = 480, }; using var manager = new WebView2Manager(panel, Dispatcher.CurrentDispatcher); manager.Register(source); var webView = panel.Children.OfType().First(); source.Width = 1024; source.Height = 768; Assert.Equal(1920.0, webView.Width); Assert.Equal(1080.0, webView.Height); }); } [Fact] public void WebView2_Control_Size_Ignores_Zero_Sized_Source() { _app.Run(() => { var panel = new Canvas(); var source = new Source { Type = SourceType.WebSource, Width = 0, Height = 0, }; using var manager = new WebView2Manager(panel, Dispatcher.CurrentDispatcher); manager.Register(source); var webView = panel.Children.OfType().First(); Assert.Equal(1920.0, webView.Width); Assert.Equal(1080.0, webView.Height); }); } [Fact] public void WebView2_DefaultBackgroundColor_Is_Transparent() { _app.Run(() => { var panel = new Canvas(); var source = new Source { Type = SourceType.WebSource, Width = 640, Height = 480, }; using var manager = new WebView2Manager(panel, Dispatcher.CurrentDispatcher); manager.Register(source); var webView = panel.Children.OfType().First(); Assert.Equal(System.Drawing.Color.Transparent, webView.DefaultBackgroundColor); }); } [Fact] public void FindContentBounds_Returns_Bounding_Box_Of_NonTransparent_Pixels() { const int w = 10, h = 6; var pixels = new byte[w * h * 4]; for (int yy = 2; yy <= 4; yy++) for (int xx = 3; xx <= 6; xx++) { var i = (yy * w + xx) * 4; pixels[i] = 255; // B pixels[i + 1] = 0; // G pixels[i + 2] = 0; // R pixels[i + 3] = 255; // A } var (x, y, bw, bh) = WebView2Manager.FindContentBounds(pixels, w, h, w * 4); Assert.Equal((3, 2, 4, 3), (x, y, bw, bh)); } [Fact] public void FindContentBounds_No_Alpha_Means_No_Crop() { const int w = 10, h = 6; var pixels = new byte[w * h * 4]; var (x, y, bw, bh) = WebView2Manager.FindContentBounds(pixels, w, h, w * 4); Assert.Equal((0, 0, w, h), (x, y, bw, bh)); } [Fact] public void FindContentBounds_Full_Opacity_Means_No_Crop() { const int w = 10, h = 6; var pixels = new byte[w * h * 4]; for (var i = 3; i < pixels.Length; i += 4) pixels[i] = 255; var (x, y, bw, bh) = WebView2Manager.FindContentBounds(pixels, w, h, w * 4); Assert.Equal((0, 0, w, h), (x, y, bw, bh)); } [Fact] public async Task CaptureScheduler_Drops_Ticks_While_Capture_InFlight_And_Resumes() { // The ~30Hz recording cadence would stack concurrent CapturePreviewAsync calls // (full-HD PNG encode is ~10-30ms) without the latest-wins drop: ticks that // arrive while a capture is in flight must be skipped, never queued, and a // capture can always start once the previous one has finished. var calls = 0; var signal = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var scheduler = new CaptureScheduler(Dispatcher.CurrentDispatcher, 200, async () => { calls++; await signal.Task; }); scheduler.Tick(); scheduler.Tick(); scheduler.Tick(); Assert.Equal(1, calls); // overlapping ticks dropped, never queued signal.SetResult(); await scheduler.Pending; // durable: Pending completes only after the drop resets scheduler.Tick(); Assert.Equal(2, calls); // once idle, the next tick captures again await scheduler.Pending; } }