using System; using System.Collections.Generic; using System.IO; using System.Linq; using AcDream.App.UI; using AcDream.App.UI.Layout; using AcDream.Content; using DatReaderWriter; using DatReaderWriter.Options; namespace AcDream.App.Tests.UI.Layout; /// /// The Journal panel's buttons must be clickable when built from the REAL /// authored layout. /// /// /// /// Every button on this panel authors property 0x0D, which was read as /// "starts disabled" — so all of them built visible and dead. The hermetic /// UiButton tests could not catch it because they construct their own /// ElementInfo; only the installed layout carries the property. /// /// /// This asserts the two things the player actually experiences: the button is /// enabled, and a click at its centre reaches IT rather than falling through to /// whatever is behind. /// /// [Trait("Lane", "InstalledDat")] public sealed class JournalPanelButtonsAreClickableTests { private static readonly (uint Id, string Name)[] Buttons = [ (0x100005DCu, "Abandon (contracts)"), (0x10000567u, "New"), (0x1000056Fu, "First"), (0x10000571u, "Last"), (0x10000565u, "Previous"), (0x10000566u, "Next"), (0x10000574u, "Record"), (0x1000057Du, "Start"), (0x10000585u, "Delete (page list)"), (0x10000588u, "Reset (page list)"), ]; private static ImportedLayout BuildPanel() { string? datDir = Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR"); if (string.IsNullOrWhiteSpace(datDir) || !Directory.Exists(datDir)) { datDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Documents", "Asheron's Call"); } if (!Directory.Exists(datDir)) { Assert.Fail( "Lane=InstalledDat requires an installed retail DAT directory; " + "see docs/release-gate.md."); } using var dats = new DatCollection(datDir, DatAccessType.Read); using var adapter = new DatCollectionAdapter(dats); ElementInfo? root = LayoutImporter.ImportInfos( adapter, JournalPanelController.HostLayoutId, JournalPanelController.SlotElementId); Assert.NotNull(root); var strings = new DatStringResolver(adapter); return LayoutImporter.Build(root!, _ => (0u, 0, 0), null, _ => null, strings.Resolve); } [Fact] public void EveryJournalPanelButtonBuildsEnabled() { ImportedLayout layout = BuildPanel(); var dead = new List(); foreach ((uint id, string name) in Buttons) { if (layout.FindElement(id) is not UiButton button) { dead.Add($"{name} (0x{id:X8}) did not build as a UiButton"); continue; } if (!button.Enabled) dead.Add($"{name} (0x{id:X8}) built DISABLED"); } Assert.Empty(dead); } [Fact] public void EveryJournalPanelButtonIsReachableByAClickAtItsCentre() { // Enabled alone is not enough — the click has to actually land on it // rather than pass through to something behind. ImportedLayout layout = BuildPanel(); var unreachable = new List(); foreach ((uint id, string name) in Buttons) { if (layout.FindElement(id) is not UiButton button) continue; UiElement? hit = HitTestSelf(button); if (!ReferenceEquals(hit, button)) unreachable.Add($"{name} (0x{id:X8}) hit-tested to {hit?.GetType().Name ?? "nothing"}"); } Assert.Empty(unreachable); } /// Hit-tests a button at its own centre, in its own local space. private static UiElement? HitTestSelf(UiButton button) { var hitTest = typeof(UiElement).GetMethod( "HitTest", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!; return hitTest.Invoke( button, [button.Width / 2f, button.Height / 2f]) as UiElement; } }