diff --git a/tests/AcDream.App.Tests/UI/MossTankMarkupBuildOverRealFilesTests.cs b/tests/AcDream.App.Tests/UI/MossTankMarkupBuildOverRealFilesTests.cs index b5f9784b..a09a59ae 100644 --- a/tests/AcDream.App.Tests/UI/MossTankMarkupBuildOverRealFilesTests.cs +++ b/tests/AcDream.App.Tests/UI/MossTankMarkupBuildOverRealFilesTests.cs @@ -181,6 +181,168 @@ public sealed class MossTankMarkupBuildOverRealFilesTests + $"the repositioned category list (left edge {categoryList.Left})."); } + /// + /// D-6 (round E architecture re-check): every real-file re-layout pin + /// so far only inspects ONE named element after a resize (the + /// Monsters list, the Advanced Options option/category lists). This + /// proves the whole resolved tree, at both ends of the main panel's + /// declared resize range — the floor (minw/minh, 856x236) and one + /// enlarged size (1100x320) past the 984x271 authored default — has + /// no sibling overlap and no child crossing its parent's edge, for + /// EACH of the nine tabs in turn (only the active tab's own subtree + /// gets a real anchor pass; DrawSelfAndChildren returns early for an + /// invisible element, matching 's + /// own reasoning). Reuses the same rectangle-overlap/parent-bounds + /// algorithm MossTankMarkupContractTests' AssertNoSiblingOverlap/ + /// AssertWithinParent apply to the AUTHORED XElement tree, adapted + /// here to the RESOLVED UiElement tree (real Left/Top/Width/Height + /// post-anchor, not the raw x/y/w/h attributes) since a resize only + /// exists at this level. + /// + [Theory] + [InlineData(856f, 236f)] // the panel's own minw/minh floor + [InlineData(1100f, 320f)] // one enlarged size past the 984x271 default + public void ResolvedMainPanelHasNoOverlapOrOutOfBoundsChildAtThisSize( + float width, float height) + { + string xml = File.ReadAllText( + Path.Combine(MossTankMarkupDirectory, "mosstank.xml")); + + var device = new RecordingGpuDevice(); + var renderer = new TextRenderer(device, new NullGpuFrameSource(), "unused"); + renderer.Begin(new Vector2(1400f, 900f)); + var ctx = new UiRenderContext(renderer, new Vector2(1400f, 900f)); + + // A fresh MarkupDocument.Build per tab, not one shared tree reused + // across all nine: each element's anchor margins are captured ONCE, + // on its very first ApplyAnchor call, and never recomputed after — + // reusing one tree across tabs would let an earlier tab's resize + // leave the shared root already at the target size by the time a + // LATER tab's own descendants take their "first" (authored-default) + // capture, corrupting their baseline. + for (int tabIndex = 0; tabIndex < 9; tabIndex++) + { + var panel = new MossTankPanel(new StubHost()); + UiNineSlicePanel built = MarkupDocument.Build(xml, panel, static id => (id, 32, 32)); + built.Visible = true; + + UiPanel[] tabGroups = built.Children + .Where(static child => child.GetType() == typeof(UiPanel)) + .Cast() + .ToArray(); + Assert.Equal(9, tabGroups.Length); + foreach (UiPanel group in tabGroups) + group.Visible = false; + tabGroups[tabIndex].Visible = true; + + // First draw at the authored default captures every anchored + // descendant's baseline margins (matching a live window that + // just opened); only THEN does resizing to the target size + // reapply those captured margins against the new bounds — + // the same two-draw shape WideningTheRealMainPanelWidensTheRealMonstersList + // uses. + built.DrawSelfAndChildren(ctx); + built.Width = width; + built.Height = height; + built.DrawSelfAndChildren(ctx); + + AssertResolvedWithinParent(built); + AssertResolvedNoSiblingOverlap(built); + } + } + + private static void AssertResolvedWithinParent(UiElement parent) + { + foreach (UiElement child in parent.Children) + { + // An invisible child's OWN box (e.g. an inactive tab group, + // whose top-level anchor still tracks the resize regardless + // of visibility) is still worth checking, but DrawSelfAndChildren + // returns before applying anchor to ITS descendants while it + // stays invisible — recursing further would compare stale, + // never-resolved-for-this-size geometry and misreport it as + // broken. Skip descending past an invisible node; the tab it + // belongs to gets its own real pass in this test's own loop + // once it becomes the active one. + if (!child.Visible) + continue; + // UiLabel is excluded from the size checks (not from + // recursion): UiLabel.OnDraw (UiPanel.cs, the CT-GF1 fix) + // deliberately overwrites Width/Height to the CURRENT measured + // text extent on every draw, ignoring its own authored w/h — + // "a markup