using System; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using Microsoft.Data.Sqlite; using Xunit; using ytLive.Models; using ytLive.Services; using ytLive.ViewModels; namespace ytLive.Tests; /// /// 2026-11-05 report: a video layer added and playing showed NOTHING in the preview pane. The decode /// was healthy — the preview's Image is switched on by a whitelist of DataTriggers (image, /// chat, alert, ticker, scoreboard, text, web, webcam) and a video layer matched none of them, so its /// Image stayed Collapsed. Same class of defect as the invisible alert box (2026-09-26). /// These assert the two ends: the layer receives frames, and the pane actually shows it. /// [Collection("RealApp")] public sealed class MediaPreviewDisplayTests { private readonly RealAppHost _app; public MediaPreviewDisplayTests(RealAppHost app) => _app = app; [Fact] public void VideoLayer_IsDrawnByThePreviewPane() { _app.Run(() => { var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-test-{Guid.NewGuid():N}.db"); var videoPath = Path.Combine(Path.GetTempPath(), "preview-clip.mp4"); var decoders = new System.Collections.Generic.List(); MainViewModel.LayoutPathOverride = tempDb; MainViewModel.MediaFrameSourceFactoryOverride = key => { var decoder = new FakeMediaSource(key); decoders.Add(decoder); return decoder; }; MainViewModel.ChromaProbeOverride = new NoProbe(); var window = new MainWindow(); var vm = (MainViewModel)window.DataContext; try { window.Show(); window.UpdateLayout(); vm.AddMediaSource(videoPath); var layer = vm.StagedScene!.Elements.OfType().Single(s => s.IsMediaSource); // The layer receives a decoded frame… var frame = SolidFrame(64, 36); decoders.Single().Emit(frame); Assert.NotNull(layer.DisplaySource); window.UpdateLayout(); // …and the preview pane actually renders it. Without the IsMediaSource trigger the // Image is Collapsed and the clip is invisible while everything else looks healthy. var pane = (UserControl)window.FindName("PreviewPane")!; var image = FindImageBoundTo(pane, layer.DisplaySource!); Assert.True(image != null, "the preview pane has no Image bound to the video layer's frame — the layer is invisible"); Assert.Equal(Visibility.Visible, image!.Visibility); Assert.True(image.ActualWidth > 0 && image.ActualHeight > 0, $"the video layer's preview Image has no size ({image.ActualWidth}x{image.ActualHeight})"); } finally { window.Close(); MainViewModel.ChromaProbeOverride = null; MainViewModel.MediaFrameSourceFactoryOverride = null; MainViewModel.LayoutPathOverride = null; SqliteConnection.ClearAllPools(); try { File.Delete(tempDb); } catch { /* best-effort cleanup */ } } }); } private static Image? FindImageBoundTo(DependencyObject parent, ImageSource source) { for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { var child = VisualTreeHelper.GetChild(parent, i); if (child is Image image && ReferenceEquals(image.Source, source)) return image; if (FindImageBoundTo(child, source) is { } found) return found; } return null; } private static VideoFrame SolidFrame(int w, int h) { var bytes = new byte[w * h * 4]; for (var i = 0; i < bytes.Length; i += 4) { bytes[i] = 40; bytes[i + 1] = 200; bytes[i + 2] = 40; bytes[i + 3] = 255; } return new VideoFrame(w, h, bytes); } private sealed class FakeMediaSource : IMediaFrameSource { public FakeMediaSource(string key) => Key = key; public string Key { get; } public bool Looping { get; set; } public event Action? FrameAvailable; public event Action? AudioAvailable { add { } remove { } } public event Action? Completed { add { } remove { } } public void Emit(VideoFrame frame) => FrameAvailable?.Invoke(frame); public Task StartAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task StopAsync() => Task.CompletedTask; public void Dispose() { } } private sealed class NoProbe : IChromaProbe { public Task ProbeAsync(string path, CancellationToken cancellationToken = default) => Task.FromResult(null); } }