using System.Linq; using System.Threading; using Xunit; using ytLive.Models; using ytLive.Services.Compositor; namespace ytLive.Tests; /// The strip's cache is a performance rule with teeth: it must rebuild when a displayed /// value moves and must NOT rebuild otherwise, or the compositor pays for a raster per tick into /// pixels that did not change. public class ScoreboardPresenterTests { private static ChatMessage Member(string name) => new() { Kind = ChatEventKind.NewMember, AuthorName = name }; [Fact] public void The_strip_rebuilds_only_when_a_displayed_value_moves() { System.Exception? failure = null; var thread = new Thread(() => { try { var recorder = new ScoreboardRecorder(); var tiles = new ScoreboardTiles { SuperChats = false, WatchHours = false, Subscribers = false, NewMembers = true, Milestones = false, LastMember = true, LastSuperChat = false, LastGift = false, }; var presenter = new ScoreboardPresenter(recorder, new ScoreboardStyle()); presenter.SetTiles(tiles); // Before a session: nothing counted, nothing to draw. Assert.Null(presenter.Frame); recorder.StartSession(); recorder.Record(Member("First")); var first = presenter.Frame; Assert.NotNull(first); // No tick, no change: the same pixels come back (the run is cached). Assert.Equal(first!.BgraPixels, presenter.Frame!.BgraPixels); // A TICK moves the window: the pixels must differ. presenter.Advance(0.5); var ticked = presenter.Frame; Assert.NotNull(ticked); Assert.NotEqual(first.BgraPixels, ticked!.BgraPixels); // SPEED is a real dial: the same elapsed time at twice the speed moves twice as far, // so the window lands somewhere else. var slow = new ScoreboardPresenter(recorder, new ScoreboardStyle()) { PixelsPerSecond = 60 }; var fast = new ScoreboardPresenter(recorder, new ScoreboardStyle()) { PixelsPerSecond = 120 }; slow.SetTiles(tiles); fast.SetTiles(tiles); slow.Advance(1.0); fast.Advance(1.0); Assert.NotEqual(slow.Frame!.BgraPixels, fast.Frame!.BgraPixels); // A visible change re-rasterises the run. recorder.Record(Member("Second")); var second = presenter.Frame; Assert.NotNull(second); Assert.NotEqual(ticked.BgraPixels, second!.BgraPixels); // A gift-recipient event changes nothing displayed: same window, same pixels. var beforeGift = presenter.Frame!.BgraPixels.ToArray(); recorder.Record(new ChatMessage { Kind = ChatEventKind.GiftReceived, AuthorName = "Recipient" }); Assert.Equal(beforeGift, presenter.Frame!.BgraPixels); // Disabling every tile returns no frame, and the layer draws nothing. presenter.SetTiles(ScoreboardTiles.None); Assert.Null(presenter.Frame); } catch (System.Exception ex) { failure = ex; } }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); Assert.Null(failure); } }