acdream/tests/AcDream.App.Tests/UI/UiScrollbarTests.cs
Erik 20ce67b625 feat(items): port retail external-container looting
Add the ClientUISystem ground-object lifecycle, authoritative root and nested ViewContents projections, replacement and close semantics, and the DAT-authored gmExternalContainerUI strip for chests and corpses.

Route double-click loot and full or partial drag transfers through the shared retail item policy without optimistic external ownership. Remove the incorrect NoLongerViewingContents behavior from owned side packs and retire AP-106/#196.

Release build succeeds and all 5,875 tests pass with five intentional skips.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-17 16:18:10 +02:00

156 lines
5.5 KiB
C#

using AcDream.App.UI;
using Xunit;
namespace AcDream.App.Tests.UI;
/// <summary>
/// Pure unit tests for <see cref="UiScrollbar.ThumbRect"/> — no GL dependency.
/// </summary>
public class UiScrollbarTests
{
// Model: content=400, view=100, trackLen=200.
// ThumbRatio = 100/400 = 0.25 → thumbH = max(8, 200*0.25) = 50.
// Travel = 200 - 50 = 150.
[Fact]
public void ThumbRect_AtStart_HasCorrectSizeAndZeroOffset()
{
var m = new UiScrollable { ContentHeight = 400, ViewHeight = 100 };
// PositionRatio = 0 (start).
var (y, h) = UiScrollbar.ThumbRect(m, trackTop: 0f, trackLen: 200f);
Assert.Equal(50f, h, 3f);
Assert.Equal(0f, y, 3f);
}
[Fact]
public void ThumbRect_AtEnd_PinsToBottomOfTrack()
{
var m = new UiScrollable { ContentHeight = 400, ViewHeight = 100 };
m.ScrollToEnd(); // PositionRatio = 1.
float trackTop = 16f, trackLen = 200f;
var (y, h) = UiScrollbar.ThumbRect(m, trackTop, trackLen);
Assert.Equal(50f, h, 3f);
// y = trackTop + travel * 1 = 16 + 150 = 166.
Assert.Equal(166f, y, 3f);
}
[Fact]
public void ThumbRect_WithButtonH_CorrectlyOffsetsFromTrackTop()
{
// Matches task spec: content=400, view=100, trackLen=200, PositionRatio=1.
// thumbH=50; travel=150; y = trackTop + 150 = trackTop + 150.
var m = new UiScrollable { ContentHeight = 400, ViewHeight = 100 };
m.ScrollToEnd();
var (y, h) = UiScrollbar.ThumbRect(m, trackTop: 16f, trackLen: 200f);
Assert.Equal(50f, h, 3f);
Assert.Equal(166f, y, 3f); // 16 + 150
}
[Fact]
public void ThumbRect_MidScroll_InterpolatesPosition()
{
// content=400 view=100 → MaxScroll=300; ScrollY=150 → PositionRatio=0.5.
var m = new UiScrollable { ContentHeight = 400, ViewHeight = 100 };
m.SetScrollY(150);
Assert.Equal(0.5f, m.PositionRatio, 3);
var (y, h) = UiScrollbar.ThumbRect(m, trackTop: 0f, trackLen: 200f);
Assert.Equal(50f, h, 3f);
// y = 0 + 150 * 0.5 = 75.
Assert.Equal(75f, y, 3f);
}
[Fact]
public void ThumbRect_SmallContent_EnforcesMinThumb()
{
// content=1000, view=10, trackLen=200 → ThumbRatio=0.01 → raw=2 < 8 → clamp to 8.
var m = new UiScrollable { ContentHeight = 1000, ViewHeight = 10 };
var (_, h) = UiScrollbar.ThumbRect(m, trackTop: 0f, trackLen: 200f);
Assert.Equal(8f, h, 3f);
}
[Fact]
public void ThumbRect_NoOverflow_ThumbFillsTrack()
{
// content <= view → ThumbRatio = 1 → thumbH = trackLen.
var m = new UiScrollable { ContentHeight = 50, ViewHeight = 100 };
var (y, h) = UiScrollbar.ThumbRect(m, trackTop: 16f, trackLen: 100f);
Assert.Equal(100f, h, 3f);
Assert.Equal(16f, y, 3f); // travel = 0 → y = trackTop
}
[Fact]
public void HorizontalScalar_clickAndDrag_updatesNormalizedValue()
{
float value = 1f;
var bar = new UiScrollbar
{
Width = 90f,
Height = 14f,
Horizontal = true,
ScalarChanged = next => value = next,
};
bar.SetScalarPosition(1f);
Assert.True(bar.OnEvent(new UiEvent(0u, bar, UiEventType.MouseDown, Data1: 8)));
Assert.Equal(0f, value, 3);
Assert.Equal(0f, bar.ScalarPosition, 3);
Assert.True(bar.OnEvent(new UiEvent(0u, bar, UiEventType.MouseMove, Data1: 45)));
Assert.Equal(0.5f, value, 3);
Assert.Equal(0.5f, bar.ScalarPosition, 3);
Assert.True(bar.OnEvent(new UiEvent(0u, bar, UiEventType.MouseUp, Data1: 45)));
}
[Fact]
public void HorizontalModel_ButtonsTrackAndThumbDriveSharedScroll()
{
var model = new UiScrollable { ContentHeight = 320, ViewHeight = 80, LineHeight = 32 };
var bar = new UiScrollbar
{
Width = 160f,
Height = 16f,
Horizontal = true,
Model = model,
};
Assert.True(bar.OnEvent(new UiEvent(0u, bar, UiEventType.MouseDown, Data1: 159)));
Assert.Equal(32, model.ScrollY);
Assert.True(bar.OnEvent(new UiEvent(0u, bar, UiEventType.MouseDown, Data1: 100)));
Assert.True(model.ScrollY >= 80);
model.SetScrollY(0);
Assert.True(bar.OnEvent(new UiEvent(0u, bar, UiEventType.MouseDown, Data1: 20)));
Assert.True(bar.OnEvent(new UiEvent(0u, bar, UiEventType.MouseMove, Data1: 144)));
Assert.Equal(model.MaxScroll, model.ScrollY);
Assert.True(bar.OnEvent(new UiEvent(0u, bar, UiEventType.MouseUp, Data1: 144)));
}
[Theory]
[InlineData(0f, 0f, 0f)]
[InlineData(0.5f, 0f, 50f)]
[InlineData(1f, 0f, 100f)]
public void ScalarFillRect_CombatPower_GrowsLeftToRight(
float fill, float expectedX, float expectedWidth)
{
var (x, width) = UiScrollbar.ScalarFillRect(100f, fill, fromRight: false);
Assert.Equal(expectedX, x, 3);
Assert.Equal(expectedWidth, width, 3);
}
[Theory]
[InlineData(0f, 104f, 0f)]
[InlineData(0.5f, 104f, 149.5f)]
[InlineData(1f, 104f, 299f)]
public void ScalarFillRect_CombatPower_StaysBetweenAuthoredLabels(
float fill, float expectedX, float expectedWidth)
{
var (x, width) = UiScrollbar.ScalarFillRect(
rangeLeft: 104f, rangeWidth: 299f, fill, fromRight: false);
Assert.Equal(expectedX, x, 3);
Assert.Equal(expectedWidth, width, 3);
}
}