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);
}
}