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 change: the SAME frame instance comes back (cache hit). Assert.Same(first, presenter.Frame); // A visible change: a new frame. recorder.Record(Member("Second")); var second = presenter.Frame; Assert.NotNull(second); Assert.NotSame(first, second); // A gift-recipient event changes nothing displayed, so the raster is reused. recorder.Record(new ChatMessage { Kind = ChatEventKind.GiftReceived, AuthorName = "Recipient" }); Assert.Same(second, presenter.Frame); // 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); } }