using AcDream.App.UI; using AcDream.App.UI.Layout; namespace AcDream.App.Tests.UI.Layout; /// /// Golden conformance tests for the vitals LayoutDesc importer. /// Uses the committed JSON fixture (vitals_2100006C.json) — no dats, no GL. /// /// These tests lock the importer's tree-building (factory dispatch, meter slice /// extraction, rects) against the real portal.dat values captured when the /// fixture was generated. Any regression in , /// , or will surface here. /// /// Sprite ids sourced from docs/research/2026-06-15-layoutdesc-format.md §11. /// [Trait("Category", "Conformance")] public class LayoutConformanceTests { // ── Test 1: Three meters at expected rects ──────────────────────────────── /// /// The three vital bars must be UiMeters positioned at x=5, width=150, height=16, /// at y=5 (health), y=21 (stamina), y=37 (mana). /// [Fact] public void VitalsTree_HasThreeMetersAtExpectedRects() { var layout = FixtureLoader.LoadVitals(); (uint Id, float Y)[] expected = [ (0x100000E6u, 5f), // health (0x100000ECu, 21f), // stamina (0x100000EEu, 37f), // mana ]; foreach (var (id, y) in expected) { var elem = layout.FindElement(id); Assert.NotNull(elem); var meter = Assert.IsType(elem); Assert.Equal(5f, meter.Left); Assert.Equal(y, meter.Top); Assert.Equal(150f, meter.Width); Assert.Equal(16f, meter.Height); } } // ── Test 2: All 18 slice ids ────────────────────────────────────────────── /// /// The six back+front 3-slice sprite ids for each of the three meters must /// match the values confirmed from the dat dump (format doc §11). /// This proves the factory's grandchild slice extraction against committed data. /// [Fact] public void VitalsTree_MetersHaveExpectedSliceIds() { var layout = FixtureLoader.LoadVitals(); // Columns: MeterId, then 6 slice ids in order: // BackLeft, BackTile, BackRight, FrontLeft, FrontTile, FrontRight (uint MeterId, uint[] Slices)[] cases = [ (0x100000E6u, [0x0600747Eu, 0x0600747Fu, 0x06007480u, 0x06007481u, 0x06007482u, 0x06007483u]), // health (0x100000ECu, [0x06007484u, 0x06007485u, 0x06007486u, 0x06007487u, 0x06007488u, 0x06007489u]), // stamina (0x100000EEu, [0x0600748Au, 0x0600748Bu, 0x0600748Cu, 0x0600748Du, 0x0600748Eu, 0x0600748Fu]), // mana ]; foreach (var (meterId, s) in cases) { var m = Assert.IsType(layout.FindElement(meterId)); Assert.Equal(s[0], m.BackLeft); Assert.Equal(s[1], m.BackTile); Assert.Equal(s[2], m.BackRight); Assert.Equal(s[3], m.FrontLeft); Assert.Equal(s[4], m.FrontTile); Assert.Equal(s[5], m.FrontRight); } } // ── Test 3: Chrome TL corner type ──────────────────────────────────────── // // NOTE: As of Task 6 (widget-generalization), Type-3 elements are built as // UiField (UIElement_Field, reg :126190) rather than UiDatElement. The // chrome corner (0x10000633) is a Type-3 dat element and is now a UiField. // Its dat sprite (0x060074C3) is not rendered by UiField — UiField renders // the focused/unfocused field background only. The sprite rendering for // Type-3 chrome image elements is a known limitation; tracked for post-Task-8 // follow-up (UiField could expose a BackgroundSprite similar to UiText). /// /// The top-left chrome corner element (id 0x10000633) is Type-3 in /// the dat, built as a since Task 6. Confirms the /// element exists in the tree. /// [Fact] public void VitalsTree_ChromeCornerHasExpectedSprite() { var layout = FixtureLoader.LoadVitals(); var elem = layout.FindElement(0x10000633u); Assert.NotNull(elem); // Type-3 elements are now built as UiField (UIElement_Field, Task 6). Assert.IsType(elem); } // ── Test 4 (N4): Inheritance resolution — FontDid propagated from base ─── /// /// Proves that Resolve()'s inheritance merge fired against real dat data: /// at least one element in the fixture tree must have FontDid == 0x40000000 /// (the vitals font), inherited from the base-layout prototype 0x10000376 /// in 0x2100003F via the BaseElement / BaseLayoutId chain. /// /// /// The three text labels (0x100000EB health, 0x100000ED stamina, /// 0x100000EF mana) are Type=0 derived elements with no own font property. /// The base element 0x10000376 carries Properties[0x1A] → /// ArrayBaseProperty[ DataIdBaseProperty{Value=0x40000000} ]. /// propagates this via the "FontDid: derived wins /// if non-zero, otherwise inherit" rule. /// /// /// /// This test verifies end-to-end inheritance resolution against the committed fixture /// (format doc §10, docs/research/2026-06-15-layoutdesc-format.md). /// It operates on the raw tree, NOT the widget tree, /// so the factory dispatch (Type 12 → skip) does not interfere. /// /// [Fact] public void VitalsTree_TextLabel_InheritsFontDidFromBaseLayout() { var root = FixtureLoader.LoadVitalsInfos(); // Walk the full ElementInfo tree and collect all FontDid values. var fontDids = new System.Collections.Generic.List(); CollectFontDids(root, fontDids); // At least one element must carry FontDid == 0x40000000 (the vitals font). // In practice, the three text labels (health/stamina/mana) all inherit it. Assert.Contains(0x40000000u, fontDids); } private static void CollectFontDids(ElementInfo node, System.Collections.Generic.List acc) { if (node.FontDid != 0) acc.Add(node.FontDid); foreach (var child in node.Children) CollectFontDids(child, acc); } // ── Test 5: Horizontal resize conformance (160→200) ────────────────────── /// /// Proves end-to-end reflow for a 160→200 width change using the corrected /// ToAnchors mapping (UIElement::UpdateForParentSizeChange @0x00462640). /// /// For each piece, margins are computed from the 160-wide design rect and then /// is applied at parentW=200. /// /// Expected outcomes: /// - TL corner (L=1,R=2): Left only → fixed at x=0, w=5 /// - top edge (L=1,R=1): Left+Right → stretches to w=190 at x=5 /// - TR corner (L=2,R=1): Right only → tracks right at x=195, w=5 /// - meter (L=1,R=1): Left+Right → stretches to w=190 at x=5 /// [Fact] public void HorizontalResize_160to200_ReflowsCorrectly() { const float designParentW = 160f; const float newParentW = 200f; const float parentH = 58f; // (piece, designX, designW, LeftEdge, RightEdge, expectedX, expectedW) (string Piece, float DesignX, float DesignW, uint L, uint R, float ExpX, float ExpW)[] cases = [ ("TL corner", 0f, 5f, 1u, 2u, 0f, 5f ), ("top edge", 5f, 150f, 1u, 1u, 5f, 190f), ("TR corner", 155f, 5f, 2u, 1u, 195f, 5f ), ("meter", 5f, 150f, 1u, 1u, 5f, 190f), ]; foreach (var (piece, dX, dW, l, r, expX, expW) in cases) { // T/B values don't affect x/w; use real vitals values (top=1, bottom=2) var anchors = ElementReader.ToAnchors(l, top: 1u, r, bottom: 2u); // Margins from the design rect at parentW=160 float mL = dX; float mR = designParentW - (dX + dW); // Reflow at parentW=200 (parentH irrelevant for x/w assertions) var (x, _, w, _) = UiElement.ComputeAnchoredRect( anchors, mL, mT: 0f, mR, mB: 0f, w0: dW, h0: 5f, parentW: newParentW, parentH); // xUnit 2.x Assert.Equal(float,float,int) = decimal-place precision Assert.True(Math.Abs(x - expX) < 0.5f, $"{piece}: expected x={expX} got {x}"); Assert.True(Math.Abs(w - expW) < 0.5f, $"{piece}: expected w={expW} got {w}"); } } }