using AcDream.App.UI; using AcDream.App.UI.Layout; namespace AcDream.App.Tests.UI.Layout; public class DatWidgetFactoryTests { private static (uint, int, int) NoTex(uint _) => (0, 0, 0); // ── Test 1: Type 7 → UiMeter ───────────────────────────────────────────── [Fact] public void Type7_Meter_MakesUiMeter() { var e = DatWidgetFactory.Create(new ElementInfo { Type = 7, Width = 150, Height = 16 }, NoTex, null); Assert.IsType(e); } // ── Test 2: Unknown type → UiDatElement fallback ───────────────────────── [Fact] public void UnknownType_FallsBackToGeneric() { var e = DatWidgetFactory.Create(new ElementInfo { Type = 999 }, NoTex, null); Assert.IsType(e); } // ── Test 3: Type 12 → null (style prototype, never rendered) ───────────── [Fact] public void Type12_StylePrototype_ReturnsNull() { var e = DatWidgetFactory.Create(new ElementInfo { Type = 12 }, NoTex, null); Assert.Null(e); } // ── Test 4: Rect + anchors set from ElementInfo ─────────────────────────── /// /// A Type-3 element with X=5,Y=21,W=150,H=16, Left=1,Top=1,Right=1 should have /// its rect + anchors copied onto the returned widget. /// Per UIElement::UpdateForParentSizeChange @0x00462640: /// Left=1 → AnchorEdges.Left (near-pin); Top=1 → AnchorEdges.Top; /// Right=1 → AnchorEdges.Right (stretch / track parent right); Bottom=0 → neither. /// Combined: Left | Top | Right. /// [Fact] public void RectAndAnchors_SetFromElementInfo() { var info = new ElementInfo { Type = 3, X = 5, Y = 21, Width = 150, Height = 16, Left = 1, Top = 1, Right = 1, Bottom = 0, }; var e = DatWidgetFactory.Create(info, NoTex, null)!; Assert.Equal(5f, e.Left); Assert.Equal(21f, e.Top); Assert.Equal(150f, e.Width); Assert.Equal(16f, e.Height); Assert.Equal(AnchorEdges.Left | AnchorEdges.Top | AnchorEdges.Right, e.Anchors); } // ── Test 5: ReadOrder propagated to ZOrder ─────────────────────────────── [Fact] public void Create_PropagatesReadOrderToZOrder() { var e = DatWidgetFactory.Create(new ElementInfo { Type = 3, ReadOrder = 7 }, NoTex, null); Assert.Equal(7, e!.ZOrder); } // ── Test G1a: Type 12 with own sprites renders; without sprites is skipped ── /// /// Task G1 change 1: only PURE Type-12 prototypes (no state media) are skipped. /// A Type-12 element that carries its own state media must return a non-null widget. /// [Fact] public void DatWidgetFactory_Type12WithMedia_Renders() { // Type 12 with a "Normal" state sprite — must render (NOT skipped). var withMedia = new ElementInfo { Type = 12, Width = 32, Height = 16, StateMedia = { ["Normal"] = (0x00001234u, 1) }, }; var e = DatWidgetFactory.Create(withMedia, NoTex, null); Assert.NotNull(e); Assert.IsType(e); // Type 12 with NO state media — must still be skipped (pure prototype). var noMedia = new ElementInfo { Type = 12 }; Assert.Null(DatWidgetFactory.Create(noMedia, NoTex, null)); } // ── Test 5c: Type 1 → UiButton ────────────────────────────────────────── [Fact] public void Type1_Button_MakesUiButton() { var e = DatWidgetFactory.Create(new ElementInfo { Type = 1, Width = 46, Height = 18 }, NoTex, null); Assert.IsType(e); } // ── Test 5b: Type 11 → UiScrollbar ────────────────────────────────────── [Fact] public void Type11_Scrollbar_MakesUiScrollbar() { var e = DatWidgetFactory.Create(new ElementInfo { Type = 11, Width = 16, Height = 68 }, NoTex, null); Assert.IsType(e); } // ── Test 6: Meter slice extraction (the important one) ─────────────────── /// /// A meter (Type 7) whose two Type-3 containers each carry 3 image children /// (ordered by X, bearing a DirectState "" sprite), plus the front container /// has a fourth expand-overlay child with ONLY a named "ShowDetail" state — /// that overlay must be excluded from the slice count. /// [Fact] public void MeterSliceExtraction_ReadsGrandchildImageIds_IgnoresOverlay() { // Slice ids sourced from format doc §11 — real health-bar ids. const uint BackL = 0x0600747Eu, BackT = 0x0600747Fu, BackR = 0x06007480u; const uint FrontL = 0x06007481u, FrontT = 0x06007482u, FrontR = 0x06007483u; const uint OverlayFile = 0x06007490u; // Back container (ReadOrder 0 — drawn first / behind) var backChild = new ElementInfo { Type = 3, ReadOrder = 0 }; backChild.Children.Add(new ElementInfo { X = 0, StateMedia = { [""] = (BackL, 1) } }); backChild.Children.Add(new ElementInfo { X = 10, StateMedia = { [""] = (BackT, 1) } }); backChild.Children.Add(new ElementInfo { X = 140, StateMedia = { [""] = (BackR, 1) } }); // Front container (ReadOrder 1 — drawn on top) var frontChild = new ElementInfo { Type = 3, ReadOrder = 1 }; frontChild.Children.Add(new ElementInfo { X = 0, StateMedia = { [""] = (FrontL, 1) } }); frontChild.Children.Add(new ElementInfo { X = 10, StateMedia = { [""] = (FrontT, 1) } }); frontChild.Children.Add(new ElementInfo { X = 140, StateMedia = { [""] = (FrontR, 1) } }); // Expand-detail overlay: named state only — NO DirectState "" — must be ignored. frontChild.Children.Add(new ElementInfo { X = 0, StateMedia = { ["ShowDetail"] = (OverlayFile, 3) } }); var meter = new ElementInfo { Type = 7, Width = 150, Height = 16 }; meter.Children.Add(backChild); meter.Children.Add(frontChild); var e = DatWidgetFactory.Create(meter, NoTex, null); var m = Assert.IsType(e); Assert.Equal(BackL, m.BackLeft); Assert.Equal(BackT, m.BackTile); Assert.Equal(BackR, m.BackRight); Assert.Equal(FrontL, m.FrontLeft); Assert.Equal(FrontT, m.FrontTile); Assert.Equal(FrontR, m.FrontRight); // Overlay (ShowDetail-only, no DirectState "") must not leak into any slice slot. Assert.NotEqual(OverlayFile, m.FrontRight); Assert.NotEqual(OverlayFile, m.FrontTile); } }