using AcDream.App.UI; using AcDream.App.UI.Layout; namespace AcDream.App.Tests.UI; /// /// FA3 carry-forward 1 (folded into Campaign FA slice FA4): unlike /// , /// must not reset the scroll offset to 0 — it exists specifically so a /// roster rebuild triggered by a server-side change (not user action) /// doesn't yank a scrolled-down user back to the top. /// public sealed class UiTemplateListBoxFlushPreservingScrollTests { private static UiTemplateListBox MakeListBox(float width, float height) { var info = new ElementInfo { Id = 0x10000279u, Type = 5u }; return new UiTemplateListBox( info, _ => (0u, 0, 0), new[] { new UiTemplateListEntry(0x21000030u, 0x10000281u) }, scrollbarElementId: 0x1000027Au) { Width = width, Height = height, }; } private static void AddRows(UiTemplateListBox box, int count, float rowHeight = 40f) { for (int i = 0; i < count; i++) box.AddPrebuiltRow(new UiText { Width = box.Width, Height = rowHeight }); } [Fact] public void Flush_ResetsScrollToZero_TheOrdinaryContract() { var box = MakeListBox(200f, 100f); AddRows(box, 10); // 400px of content in a 100px viewport box.ViewportForTest!.ApplyAnchor(box.Width, box.Height); box.ViewportForTest!.LayoutScrollableChildren(); box.Scroll.SetScrollY(150); Assert.Equal(150, box.Scroll.ScrollY); box.Flush(); Assert.Equal(0, box.Scroll.ScrollY); } [Fact] public void FlushPreservingScroll_KeepsTheScrollOffset() { var box = MakeListBox(200f, 100f); AddRows(box, 10); // 400px of content in a 100px viewport box.ViewportForTest!.ApplyAnchor(box.Width, box.Height); box.ViewportForTest!.LayoutScrollableChildren(); box.Scroll.SetScrollY(150); Assert.Equal(150, box.Scroll.ScrollY); box.FlushPreservingScroll(); Assert.Equal(150, box.Scroll.ScrollY); // Rebuild the SAME row count — the offset should still be valid // (and stay put) once the next layout pass recomputes content // height against the rebuilt rows. AddRows(box, 10); box.ViewportForTest!.LayoutScrollableChildren(); Assert.Equal(150, box.Scroll.ScrollY); } [Fact] public void FlushPreservingScroll_ClampsToTheNewShorterContent() { var box = MakeListBox(200f, 100f); AddRows(box, 10); // 400px of content box.ViewportForTest!.ApplyAnchor(box.Width, box.Height); box.ViewportForTest!.LayoutScrollableChildren(); box.Scroll.SetScrollY(300); // near the bottom of the 10-row content box.FlushPreservingScroll(); // Rebuild with far FEWER rows (e.g. most of the fellowship left). AddRows(box, 2); // 80px of content, less than the 100px viewport box.ViewportForTest!.LayoutScrollableChildren(); // Clamped to the new MaxScroll (0, since content < viewport) — // never left dangling past the real content, and never throws. Assert.Equal(0, box.Scroll.ScrollY); } [Fact] public void FlushPreservingScroll_DormantBox_IsANoOp() { var box = MakeListBox(200f, 100f); // No rows ever added — the viewport was never created. box.FlushPreservingScroll(); Assert.Null(box.ViewportForTest); } }