The character report element m_pMainText (0x1000011d) is created at RUNTIME by gmCharacterInfoUI — it is NOT in any static LayoutDesc (confirmed: acdream's dat-import of both 0x2100002E and 0x2100006E lacks it; only a runtime UI-tree capture has it). So CharacterController.Bind now CREATES the UiText report element + attaches it to the panel body (Left 12 / Top 44 / fills, ZOrder above chrome) and fills it via LinesProvider — replicating what the retail gm*UI does. Verified: the studio (--layout 0x2100002E) renders the full report — identity, birth/age/deaths, vitals, the 6 attributes, skills, augmentations, encumbrance. Tests rewritten to assert the CREATED element (10 pass); full App suite 619 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
147 lines
6 KiB
C#
147 lines
6 KiB
C#
using AcDream.App.Studio;
|
|
using AcDream.App.UI;
|
|
using AcDream.App.UI.Layout;
|
|
|
|
namespace AcDream.App.Tests.UI.Layout;
|
|
|
|
/// <summary>
|
|
/// Unit tests for <see cref="CharacterController"/> and <see cref="SampleData.SampleCharacter"/>.
|
|
/// The controller CREATES the m_pMainText report element (0x1000011d) at bind time — retail's
|
|
/// gm*UI builds it at runtime; it is not a static dat element — and attaches it to the layout root.
|
|
/// No dats, no GL — pure data-wiring + report-content tests.
|
|
/// </summary>
|
|
public class CharacterControllerTests
|
|
{
|
|
/// <summary>Bind on an empty layout, then return the created m_pMainText element.</summary>
|
|
private static UiText BindAndGetText(System.Func<CharacterSheet> data)
|
|
{
|
|
var layout = FakeLayout();
|
|
CharacterController.Bind(layout, data);
|
|
return layout.Root.Children.OfType<UiText>()
|
|
.First(t => t.EventId == CharacterController.MainTextId);
|
|
}
|
|
|
|
private static string Report(System.Func<CharacterSheet> data)
|
|
=> string.Join("\n", BindAndGetText(data).LinesProvider().Select(l => l.Text));
|
|
|
|
// ── Test 1: Bind creates m_pMainText with a non-empty report ──────────────
|
|
|
|
[Fact]
|
|
public void Bind_CreatesMainTextWithReport()
|
|
{
|
|
var text = BindAndGetText(SampleData.SampleCharacter);
|
|
Assert.True(text.LinesProvider().Count > 0, "Expected non-empty report after Bind");
|
|
}
|
|
|
|
// ── Test 2: Report contains all six attribute names (decomp order 1,2,4,3,5,6) ───
|
|
|
|
[Fact]
|
|
public void Bind_ReportContainsAllSixAttributes()
|
|
{
|
|
var allText = Report(SampleData.SampleCharacter);
|
|
Assert.Contains("Strength", allText);
|
|
Assert.Contains("Endurance", allText);
|
|
Assert.Contains("Quickness", allText);
|
|
Assert.Contains("Coordination", allText);
|
|
Assert.Contains("Focus", allText);
|
|
Assert.Contains("Self", allText);
|
|
}
|
|
|
|
// ── Test 3: Report contains vitals section ────────────────────────────────
|
|
|
|
[Fact]
|
|
public void Bind_ReportContainsVitals()
|
|
{
|
|
var allText = Report(SampleData.SampleCharacter);
|
|
Assert.Contains("Health", allText);
|
|
Assert.Contains("Stamina", allText);
|
|
Assert.Contains("Mana", allText);
|
|
}
|
|
|
|
// ── Test 4: Report contains encumbrance / burden section ─────────────────
|
|
|
|
[Fact]
|
|
public void Bind_ReportContainsBurden()
|
|
{
|
|
var allText = Report(SampleData.SampleCharacter);
|
|
Assert.Contains("Burden", allText);
|
|
Assert.Contains("Capacity", allText);
|
|
}
|
|
|
|
// ── Test 5: Report contains the sample character's name ──────────────────
|
|
|
|
[Fact]
|
|
public void Bind_ReportContainsSampleName()
|
|
=> Assert.Contains("Studio Player", Report(SampleData.SampleCharacter));
|
|
|
|
// ── Test 6: Bind on a layout WITHOUT the element creates it (no throw) ────
|
|
|
|
[Fact]
|
|
public void Bind_OnEmptyLayout_CreatesElement()
|
|
{
|
|
var layout = FakeLayout(); // no MainTextId present
|
|
CharacterController.Bind(layout, SampleData.SampleCharacter);
|
|
Assert.Contains(layout.Root.Children.OfType<UiText>(),
|
|
t => t.EventId == CharacterController.MainTextId);
|
|
}
|
|
|
|
// ── Test 7: The created element carries the m_pMainText id ───────────────
|
|
|
|
[Fact]
|
|
public void Bind_CreatedElementHasMainTextId()
|
|
=> Assert.Equal(CharacterController.MainTextId, BindAndGetText(SampleData.SampleCharacter).EventId);
|
|
|
|
// ── Test 8: SampleCharacter fixture has non-zero attribute values ─────────
|
|
|
|
[Fact]
|
|
public void SampleCharacter_AttributesAreNonZero()
|
|
{
|
|
var s = SampleData.SampleCharacter();
|
|
Assert.True(s.Strength > 0, "Strength must be > 0");
|
|
Assert.True(s.Endurance > 0, "Endurance must be > 0");
|
|
Assert.True(s.Quickness > 0, "Quickness must be > 0");
|
|
Assert.True(s.Coordination > 0, "Coordination must be > 0");
|
|
Assert.True(s.Focus > 0, "Focus must be > 0");
|
|
Assert.True(s.Self > 0, "Self must be > 0");
|
|
}
|
|
|
|
// ── Test 9: SampleCharacter fixture has coherent vitals ──────────────────
|
|
|
|
[Fact]
|
|
public void SampleCharacter_VitalsAreCoherent()
|
|
{
|
|
var s = SampleData.SampleCharacter();
|
|
Assert.True(s.HealthCurrent > 0 && s.HealthCurrent <= s.HealthMax, "Health cur must be in [1, max]");
|
|
Assert.True(s.StaminaCurrent > 0 && s.StaminaCurrent <= s.StaminaMax, "Stamina cur must be in [1, max]");
|
|
Assert.True(s.ManaCurrent > 0 && s.ManaCurrent <= s.ManaMax, "Mana cur must be in [1, max]");
|
|
}
|
|
|
|
// ── Test 10: Report contains attribute VALUES from the fixture ────────────
|
|
|
|
[Fact]
|
|
public void Bind_ReportContainsSampleAttributeValues()
|
|
{
|
|
var sheet = SampleData.SampleCharacter();
|
|
var allText = Report(() => sheet);
|
|
Assert.Contains(sheet.Strength.ToString(), allText);
|
|
Assert.Contains(sheet.Endurance.ToString(), allText);
|
|
Assert.Contains(sheet.Quickness.ToString(), allText);
|
|
Assert.Contains(sheet.Coordination.ToString(), allText);
|
|
Assert.Contains(sheet.Focus.ToString(), allText);
|
|
Assert.Contains(sheet.Self.ToString(), allText);
|
|
}
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
|
|
private static ImportedLayout FakeLayout(params (uint id, UiElement e)[] items)
|
|
{
|
|
var dict = new Dictionary<uint, UiElement>();
|
|
var root = new UiPanel();
|
|
foreach (var (id, e) in items)
|
|
{
|
|
root.AddChild(e);
|
|
dict[id] = e;
|
|
}
|
|
return new ImportedLayout(root, dict);
|
|
}
|
|
}
|