using System; using Xunit; using ytLive.Models; using ytLive.Services; namespace ytLive.Tests; /// /// Take-5 starvation fix (slice 3): the chat layer is rendered ON CHANGE, not on /// every pump tick — the per-tick WPF raster (FormattedText + RenderTargetBitmap /// + CopyPixels) was the 25ms render. OBS text sources raster-on-change; this /// pins that contract with the real renderer on the RealApp STA host: identical /// inputs reuse the cached frame by identity, and content or config changes force /// a fresh raster. /// [Collection("RealApp")] public sealed class ChatOverlayLayerCacheTests { private readonly RealAppHost _app; public ChatOverlayLayerCacheTests(RealAppHost app) => _app = app; [Fact] public void RenderFrame_RastersOnContentOrConfigChange_ReusesCacheOtherwise() { _app.Run(Run); } private void Run() { var layer = new ChatOverlayLayer(new ChatBoxRenderer()); var box = new Source { Type = SourceType.ChatBox, Name = "YouTube Chat", Width = 220, Height = 160 }; Assert.Null(layer.RenderFrame(box)); // empty buffer — nothing to rasterize layer.Messages.Add(new ChatMessage { AuthorName = "a", Message = "hello", Timestamp = DateTime.Now }); var first = layer.RenderFrame(box); Assert.NotNull(first); Assert.Same(first, layer.RenderFrame(box)); // unchanged content + config → the SAME frame Assert.Same(first, layer.RenderFrame(box)); // every tick hits the cache, not the raster var resized = new Source { Type = SourceType.ChatBox, Width = 300, Height = 160 }; var reRendered = layer.RenderFrame(resized); Assert.NotNull(reRendered); Assert.NotSame(first, reRendered); // config change → fresh raster layer.Messages.Add(new ChatMessage { AuthorName = "b", Message = "second", Timestamp = DateTime.Now }); var grown = layer.RenderFrame(box); Assert.NotNull(grown); Assert.NotSame(reRendered, grown); // new message → fresh raster layer.Messages.Clear(); // the fade path Assert.Null(layer.RenderFrame(box)); Assert.Null(layer.RenderFrame(box)); } }