acdream/tests/AcDream.App.Tests/UI/Layout/CreatureAppraisalRowsTests.cs
Erik d96ea2de98 feat(ui): complete retail creature appraisal details
Port the separate creature rating list, layer the animated preview between authored row chrome and text, and follow selection while the examination floaty is visible. Preserve the remaining item-preview and font-state gaps in AP-110.

Co-authored-by: Codex <codex@openai.com>
2026-07-23 14:04:19 +02:00

186 lines
6.3 KiB
C#

using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.Core.Items;
using AcDream.Core.Net.Messages;
namespace AcDream.App.Tests.UI.Layout;
public sealed class CreatureAppraisalRowsTests
{
[Fact]
public void FailedAssessmentShowsOnlyHealthPercentAndUnknownOtherValues()
{
var profile = Profile(
health: 25u,
healthMax: 100u,
highlights: (ushort)0,
colors: (ushort)0);
IReadOnlyList<CreatureAppraisalRow> rows =
CreatureAppraisalRows.Build(profile, success: false);
Assert.All(
rows,
row => Assert.Equal(
CreatureAppraisalValueStyle.Incomplete,
row.Style));
Assert.Equal("25 %", rows[6].Value);
Assert.Equal("???", rows[0].Value);
Assert.Equal("???", rows[7].Value);
Assert.Equal("???", rows[8].Value);
}
[Fact]
public void EnchantmentBitsSelectRetailPositiveAndNegativeStyles()
{
var profile = Profile(
health: 100u,
healthMax: 100u,
highlights: (ushort)((1 << 0) | (1 << 6)),
colors: (ushort)(1 << 0));
IReadOnlyList<CreatureAppraisalRow> rows =
CreatureAppraisalRows.Build(profile, success: true);
Assert.Equal(CreatureAppraisalValueStyle.Positive, rows[0].Style);
Assert.Equal(CreatureAppraisalValueStyle.Negative, rows[6].Style);
Assert.Equal(CreatureAppraisalValueStyle.Normal, rows[1].Style);
}
[Fact]
public void ExtraRatingsFollowRetailGroupingFormattingAndSeparators()
{
var properties = new PropertyBundle();
properties.Ints[0x133u] = 35;
properties.Ints[0x139u] = 7;
properties.Ints[0x13Au] = 4;
properties.Ints[0x134u] = 12;
properties.Ints[0x13Bu] = 3;
properties.Ints[0x13Cu] = 2;
properties.Ints[0x15Eu] = 9;
properties.Ints[0x15Fu] = 6;
IReadOnlyList<CreatureAppraisalRow> rows =
CreatureAppraisalRows.BuildExtra(properties);
Assert.Equal(5, rows.Count);
Assert.Equal(("", ""), (rows[0].Label, rows[0].Value));
Assert.Equal(
("Dmg/CritDmg", "Rating: 35/4"),
(rows[1].Label, rows[1].Value));
Assert.Equal(
("Dmg/CritDmg", "Resist: 12/2"),
(rows[2].Label, rows[2].Value));
Assert.Equal(
("DoT/Life:", "Resist: 9/6"),
(rows[3].Label, rows[3].Value));
Assert.Equal(("", ""), (rows[4].Label, rows[4].Value));
}
[Fact]
public void CritOnlyTriggersRatingRowWhileHealingBoostAloneDoesNot()
{
var critOnly = new PropertyBundle();
critOnly.Ints[0x139u] = 8;
IReadOnlyList<CreatureAppraisalRow> rows =
CreatureAppraisalRows.BuildExtra(critOnly);
Assert.Equal(3, rows.Count);
Assert.Equal("Rating: 0/0", rows[1].Value);
var healingOnly = new PropertyBundle();
healingOnly.Ints[0x143u] = 20;
Assert.Empty(CreatureAppraisalRows.BuildExtra(healingOnly));
}
[Fact]
public void AuthoredRowTemplateCarriesRetailOverlappingLabelValueGeometry()
{
ElementInfo template =
FixtureLoader.LoadExaminationRowTemplateInfos();
Assert.Equal(CreatureAppraisalRowTemplateFactory.TemplateId, template.Id);
Assert.Equal((292f, 20f), (template.Width, template.Height));
ElementInfo label = Assert.Single(
template.Children,
child => child.Id == CreatureAppraisalRowTemplateFactory.LabelId);
ElementInfo value = Assert.Single(
template.Children,
child => child.Id == CreatureAppraisalRowTemplateFactory.ValueId);
Assert.Equal((0f, 128f), (label.X, label.Width));
Assert.Equal((27f, 256f), (value.X, value.Width));
Assert.Equal(0x40000001u, label.FontDid);
Assert.Equal(0x40000001u, value.FontDid);
}
[Fact]
public void CreatureNameResolverUsesLoadedRetailMappingAndSafeFallback()
{
var resolver = new CreatureDisplayNameResolver(
new Dictionary<uint, string> { [77u] = "Ghost" });
Assert.Equal("Ghost", resolver.Resolve(77));
Assert.Equal(string.Empty, resolver.Resolve(0));
Assert.Equal(string.Empty, resolver.Resolve(999));
}
[Fact]
public void LayeredTemplateSeparatesChromeFromForegroundText()
{
var templates = new CreatureAppraisalRowTemplateFactory(
FixtureLoader.LoadExaminationRowTemplateInfos(),
_ => (0u, 0, 0),
defaultFont: null);
var row = new CreatureAppraisalRow(
"Strength",
"120",
CreatureAppraisalValueStyle.Normal);
UiTemplateListSlot background = templates.Create(
row,
CreatureAppraisalRowLayer.Background);
UiTemplateListSlot foreground = templates.Create(
row,
CreatureAppraisalRowLayer.Foreground);
Assert.Empty(((UiText)background.Content.FindElement(
CreatureAppraisalRowTemplateFactory.LabelId)!).LinesProvider());
Assert.Empty(((UiText)background.Content.FindElement(
CreatureAppraisalRowTemplateFactory.ValueId)!).LinesProvider());
Assert.False(
Assert.IsType<UiDatElement>(foreground.Content.Root).MediaVisible);
Assert.Equal(
"Strength",
Assert.Single(((UiText)foreground.Content.FindElement(
CreatureAppraisalRowTemplateFactory.LabelId)!)
.LinesProvider()).Text);
Assert.Equal(
"120",
Assert.Single(((UiText)foreground.Content.FindElement(
CreatureAppraisalRowTemplateFactory.ValueId)!)
.LinesProvider()).Text);
}
private static AppraiseInfoParser.CreatureProfile Profile(
uint health,
uint healthMax,
ushort highlights,
ushort colors)
=> new(
Flags: 0x09u,
Health: health,
HealthMax: healthMax,
Strength: null,
Endurance: null,
Quickness: null,
Coordination: null,
Focus: null,
Self: null,
Stamina: 50u,
Mana: 75u,
StaminaMax: 100u,
ManaMax: 100u,
AttributeHighlights: highlights,
AttributeColors: colors);
}