using System; using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; using System.Windows.Threading; using Xunit; using ytLive.Models; using ytLive.ViewModels; namespace ytLive.Tests; /// /// Reproduces the real MainWindow preview to check the round-clip corner /// handle: whether a click at the corner actually stays inside the preview /// (doesn't fall through and deselect the source) and whether the round clip /// renders as a circle rather than an oval. /// public sealed class RoundClipInteractionTests { [Fact] public void Round_Clip_Corner_Is_Grabbable_And_Shape_Is_Circle() { Exception? failure = null; var thread = new Thread(() => { try { Run(); } catch (Exception ex) { failure = ex; } }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); if (failure != null) throw new Xunit.Sdk.XunitException("Round-clip interaction failed: " + failure); } private void Run() { var app = new App(); app.InitializeComponent(); var window = new MainWindow(); var vm = (MainViewModel)window.DataContext; try { window.Show(); window.UpdateLayout(); var scene = vm.ActiveScene!; var source = new Source { Name = "Webcam", Type = SourceType.Webcam, DeviceId = "test-camera", X = 1408, Y = 778, Width = 480, Height = 270, }; scene.Sources.Add(source); vm.SelectedSource = source; window.UpdateLayout(); var previewGrid = (Grid)window.FindName("PreviewGrid")!; var canvasGrid = (Grid)window.FindName("CanvasGrid")!; var toWindow = canvasGrid.TransformToVisual(window); var corner = new Point(source.X + source.Width, source.Y + source.Height); foreach (var shape in new[] { ClipShape.Traditional, ClipShape.Round }) { source.ClipShape = shape; window.UpdateLayout(); var cornerInWindow = toWindow.Transform(corner); // A click on the corner must land inside the preview (descendant // of PreviewGrid); otherwise Window_PreviewMouseLeftButtonDown // deselects the source and the handle can never be grabbed. var hit = VisualTreeHelper.HitTest(window, cornerInWindow); var grabbable = hit != null && IsDescendantOf(hit.VisualHit, previewGrid); Assert.True(grabbable, $"{shape}: corner click fell through to '{hit?.VisualHit.GetType().Name ?? "null"}' " + "so the source gets deselected before the resize handler runs"); } // The round clip must render as a circle (square bounding box), not an oval. source.ClipShape = ClipShape.Round; window.UpdateLayout(); var ellipse = FindRoundEllipse(window); Assert.NotNull(ellipse); Assert.Equal(ellipse!.RenderSize.Width, ellipse.RenderSize.Height, 1.0); } finally { window.Close(); } } private static Ellipse? FindRoundEllipse(Window window) { return Walk(window, element => element is Ellipse e && e.IsVisible) as Ellipse; } private static DependencyObject? Walk(DependencyObject parent, Func predicate) { for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { var child = VisualTreeHelper.GetChild(parent, i); if (predicate(child)) return child; var found = Walk(child, predicate); if (found != null) return found; } return null; } private static bool IsDescendantOf(DependencyObject? child, DependencyObject ancestor) { while (child != null && !ReferenceEquals(child, ancestor)) child = VisualTreeHelper.GetParent(child); return child != null; } }