acdream/tests/AcDream.App.Tests/UI/UiItemListScrollTests.cs
Erik 15aa3b9aff fix(ui): complete retail inventory scroll polish
Preserve pixel scroll offsets across inventory rebuilds, crop partially visible rows with nested geometry/UV clips, and replace the obsolete 560px resize ceiling with available screen height. Keep retail's row-sized wheel step while allowing continuous scrollbar thumb positions.

Co-Authored-By: Codex <codex@openai.com>
2026-07-11 10:56:33 +02:00

117 lines
4.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AcDream.App.UI;
using Xunit;
namespace AcDream.App.Tests.UI;
public sealed class UiItemListScrollTests
{
private static UiItemList Grid(int items)
{
var list = new UiItemList { Columns = 6, CellWidth = 32, CellHeight = 32, Width = 192, Height = 96 };
list.Flush(); // drop the default single cell
for (int i = 0; i < items; i++) list.AddItem(new UiItemSlot());
list.LayoutCells(); // drive the scroll model + position cells
return list;
}
[Fact]
public void RowCount_ceils_to_whole_rows()
{
Assert.Equal(0, UiItemList.RowCount(0, 6));
Assert.Equal(1, UiItemList.RowCount(1, 6));
Assert.Equal(7, UiItemList.RowCount(41, 6));
}
[Fact]
public void Grid_drives_scroll_model_from_content()
{
var list = Grid(30); // 5 rows × 32 = 160 content, 96 view
Assert.Equal(160, list.Scroll.ContentHeight);
Assert.Equal(96, list.Scroll.ViewHeight);
Assert.Equal(64, list.Scroll.MaxScroll);
Assert.True(list.Scroll.HasOverflow);
}
[Fact]
public void At_top_first_three_rows_visible_rest_clipped()
{
var list = Grid(30);
Assert.True(list.GetItem(0)!.Visible); // row 0, top 0
Assert.True(list.GetItem(12)!.Visible); // row 2, top 64 (+32 == 96)
Assert.False(list.GetItem(18)!.Visible); // row 3, top 96 (off the bottom)
}
[Fact]
public void Scrolled_one_row_shifts_window_and_clips_top_row()
{
var list = Grid(30);
list.Scroll.SetScrollY(32);
list.LayoutCells();
Assert.False(list.GetItem(0)!.Visible); // row 0 scrolled above
Assert.Equal(0f, list.GetItem(6)!.Top); // row 1 now at the view top
Assert.True(list.GetItem(18)!.Visible); // row 3 now visible
}
[Fact]
public void Pixel_scroll_keeps_partiallyVisible_edge_rows()
{
var list = Grid(30);
list.Scroll.SetScrollY(8);
list.LayoutCells();
Assert.True(list.GetItem(0)!.Visible);
Assert.Equal(-8f, list.GetItem(0)!.Top);
Assert.True(list.GetItem(18)!.Visible);
Assert.Equal(88f, list.GetItem(18)!.Top);
Assert.False(list.GetItem(24)!.Visible);
Assert.Null(list.HitTest(5f, -1f));
Assert.IsType<UiItemSlot>(list.HitTest(5f, 1f));
}
[Fact]
public void Deferred_rebuild_preserves_pixel_scroll_offset()
{
var list = Grid(30);
list.Scroll.SetScrollY(40);
using (list.DeferLayout())
{
list.Flush();
for (int i = 0; i < 30; i++)
list.AddItem(new UiItemSlot());
}
Assert.Equal(40, list.Scroll.ScrollY);
Assert.Equal(-8f, list.GetItem(6)!.Top);
}
[Fact]
public void Scroll_survives_the_per_frame_anchor_pass()
{
// Regression (the escaping-grid bug): UiElement.DrawSelfAndChildren runs ApplyAnchor
// on every child AFTER OnDraw/LayoutCells. Grid cells must be exempt (Anchors=None) so
// the anchor pass can't reset the scroll offset LayoutCells applied — otherwise the
// grid translates out of the view when scrolled.
var list = Grid(30); // cells added (Anchors=None) + laid out
for (int i = 0; i < list.GetNumUIItems(); i++) // first anchor pass (would capture base)
list.GetItem(i)!.ApplyAnchor(list.Width, list.Height);
list.Scroll.SetScrollY(32); // scroll one row
list.LayoutCells();
for (int i = 0; i < list.GetNumUIItems(); i++) // second anchor pass (the draw traversal)
list.GetItem(i)!.ApplyAnchor(list.Width, list.Height);
Assert.Equal(0f, list.GetItem(6)!.Top); // row 1 stays at the view top
Assert.False(list.GetItem(0)!.Visible); // row 0 stays clipped above
}
[Fact]
public void Wheel_down_scrolls_toward_the_bottom()
{
var list = Grid(30); // max scroll 64, LineHeight 32
// Silk wheel +Y = up/older; UiText negates Data0. Wheel DOWN (Data0 < 0) → +ScrollY.
var e = new UiEvent(0u, null, UiEventType.Scroll, Data0: -1);
bool handled = list.OnEvent(e);
Assert.True(handled);
Assert.Equal(32, list.Scroll.ScrollY); // one line (32px) down
}
}