using AcDream.App.Studio; using AcDream.App.UI; namespace AcDream.App.Tests.Studio; /// /// Tests for — parsing the retail UI dump JSON and /// building a tree from it. /// /// These tests load the real dump file from the source tree /// (docs/research/2026-06-25-retail-ui-layout-dump.json). The test /// skips cleanly when the file is absent (should not happen in a normal dev /// checkout, but guards against stripped CI machines). /// public class DumpLayoutTests { private static string DumpPath() { // Walk up from the test output directory to the solution root, // mirroring ConformanceDats.SolutionRoot(). var dir = AppContext.BaseDirectory; while (!string.IsNullOrEmpty(dir)) { if (File.Exists(Path.Combine(dir, "AcDream.slnx"))) return Path.Combine(dir, "docs", "research", "2026-06-25-retail-ui-layout-dump.json"); dir = Path.GetDirectoryName(dir); } // Fallback: try a relative path (won't find it but skip rather than throw) return Path.Combine(AppContext.BaseDirectory, "docs", "research", "2026-06-25-retail-ui-layout-dump.json"); } private static (uint, int, int) NoTex(uint _) => (1u, 1, 1); // ── Helpers ────────────────────────────────────────────────────────── /// Depth-first search for an element with the given EventId. private static UiElement? FindById(UiElement root, uint id) { if (root.EventId == id) return root; foreach (var c in root.Children) { var found = FindById(c, id); if (found is not null) return found; } return null; } /// Count the total elements in the tree (self + all descendants). private static int CountAll(UiElement root) { int n = 1; foreach (var c in root.Children) n += CountAll(c); return n; } // ── Tests ───────────────────────────────────────────────────────────── /// /// Loading the "inventory" slug should succeed and the returned tree should /// contain an element with EventId == 0x100001D5 (the doll viewport node) /// and at least 40 elements in total. /// [Fact] public void Load_Inventory_ReturnsTreeWithDollViewport() { var path = DumpPath(); if (!File.Exists(path)) return; // Skip: dump not available. var root = DumpLayout.Load(path, "inventory", NoTex, out var err); Assert.NotNull(root); Assert.Null(err); // The doll viewport element must appear somewhere in the tree. const uint dollViewportId = 0x100001D5u; var found = FindById(root!, dollViewportId); Assert.NotNull(found); // The full tree must be reasonably deep — 59 dump nodes → >= 40 elements. int total = CountAll(root!); Assert.True(total >= 40, $"Expected >= 40 elements in inventory tree; got {total}"); } /// /// Loading an unknown slug must return null and a non-empty error string. /// [Fact] public void Load_UnknownSlug_ReturnsNullWithError() { var path = DumpPath(); if (!File.Exists(path)) return; // Skip. var root = DumpLayout.Load(path, "this_slug_does_not_exist", NoTex, out var err); Assert.Null(root); Assert.NotNull(err); Assert.NotEmpty(err!); } /// /// The root element's Left/Top should be (0,0) (the panel's rect offset has /// been stripped so the tree sits at the window origin), and its Width/Height /// should match the dump panel dimensions. /// [Fact] public void Load_Inventory_RootAtOrigin() { var path = DumpPath(); if (!File.Exists(path)) return; // Skip. var root = DumpLayout.Load(path, "inventory", NoTex, out _); Assert.NotNull(root); // Root always placed at (0,0) by DumpLayout (origin of the UiHost). Assert.Equal(0f, root!.Left); Assert.Equal(0f, root.Top); // Width/Height come from the panel record in the dump. Assert.True(root.Width > 0, "Root width must be > 0"); Assert.True(root.Height > 0, "Root height must be > 0"); } /// /// Children must use parent-relative coordinates (the dump rects are absolute; /// DumpLayout subtracts the parent rect to produce parent-local offsets). /// Verify that at least the direct children of the root have Left/Top values /// that are NOT equal to the absolute rect they had in the dump (since the root /// was at x>0 in screen space but we place it at 0,0). /// [Fact] public void Load_Inventory_ChildrenAreParentRelative() { var path = DumpPath(); if (!File.Exists(path)) return; // Skip. var root = DumpLayout.Load(path, "inventory", NoTex, out _); Assert.NotNull(root); // If the dump has children at absolute x>=500 but the root is at 0, // a correct parent-relative placement will give children x < 500. // (The inventory panel root is at absolute x=500; children in the dump // also start at x=500 — after subtraction they should land near x=0.) bool anyChildAtAbsoluteX = false; foreach (var child in root!.Children) { if (child.Left >= 490f) // would indicate absolute not relative { anyChildAtAbsoluteX = true; break; } } Assert.False(anyChildAtAbsoluteX, "Children appear to have absolute coords (Left >= 490) — " + "DumpLayout must subtract the parent rect."); } /// /// Every panel slug known in the dump must load without error. /// This is a smoke test that the JSON parse + tree build does not /// crash on any of the 26 panels. /// [Fact] public void Load_AllSlugs_Succeed() { var path = DumpPath(); if (!File.Exists(path)) return; // Skip. var slugs = UiDumpModel.ListSlugs(path); Assert.True(slugs.Count >= 20, $"Expected >= 20 panel slugs in dump; got {slugs.Count}"); foreach (var slug in slugs) { var root = DumpLayout.Load(path, slug, NoTex, out var err); Assert.True(root is not null || err is not null, $"Load('{slug}') returned both null root AND null error — one must be set."); if (root is null) Assert.Fail($"Slug '{slug}' failed with error: {err}"); } } }