using System; 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; /// /// Campaign QT slice QT5: pins the authored facts the Journal panel is built /// on, against the live installed DATs rather than a committed fixture. /// /// /// These are the facts a reader would otherwise have to take on trust from a /// commit message: the panel's slot key, its three-tab table, and which tab is /// the authored default. The FA campaign had to CORRECT a tab pairing that had /// been inferred from x-order, which is why the pairing is asserted here from /// the authored 0x2E table itself. /// [Trait("Lane", "InstalledDat")] public sealed class JournalPanelSlotProbeTests { private const uint SlotKeyPropertyId = 0x10000029u; private const uint TabTablePropertyId = 0x2Eu; private const uint TabButtonPropertyId = 0x30u; private const uint TabPagePropertyId = 0x31u; private const uint TabDefaultPropertyId = 0x32u; private const uint ToolbarLayoutId = 0x21000016u; private const uint JournalToolbarButtonId = 0x1000055Au; private static ElementInfo Import(uint layoutId, uint rootElementId = 0u) { string? datDir = ContentConformanceDatDir(); if (datDir is null) { 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? info = rootElementId == 0u ? LayoutImporter.ImportInfos(adapter, layoutId) : LayoutImporter.ImportInfos(adapter, layoutId, rootElementId); Assert.NotNull(info); return info!; } private static string? ContentConformanceDatDir() { string? fromEnv = Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR"); if (!string.IsNullOrWhiteSpace(fromEnv) && Directory.Exists(fromEnv)) return fromEnv; string def = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Documents", "Asheron's Call"); return Directory.Exists(def) ? def : null; } private static UiPropertyValue? Property(ElementInfo info, uint propertyId) { foreach (UiStateInfo state in info.States.Values) { if (state.Properties.Values.TryGetValue(propertyId, out UiPropertyValue? value)) return value; } return null; } [Fact] public void TheSlotAuthorsTheCatalogPanelId() { ElementInfo slot = Import( JournalPanelController.HostLayoutId, JournalPanelController.SlotElementId); UiPropertyValue? key = Property(slot, SlotKeyPropertyId); Assert.NotNull(key); Assert.Equal(RetailPanelCatalog.Journal, (uint)key!.UnsignedValue); } [Fact] public void TheAuthoredTabTablePairsContractsWithTheGmContractsUiPage() { // Read from the table, never inferred from x-order. ElementInfo slot = Import( JournalPanelController.HostLayoutId, JournalPanelController.SlotElementId); UiPropertyValue? tabs = Property(slot, TabTablePropertyId); Assert.NotNull(tabs); Assert.Equal(3, tabs!.ArrayValue.Count); UiPropertyValue first = tabs.ArrayValue[0]; Assert.Equal( 0x100005D3u, (uint)first.StructValue[TabButtonPropertyId].UnsignedValue); Assert.Equal( JournalPanelController.ContractsPageId, (uint)first.StructValue[TabPagePropertyId].UnsignedValue); } [Fact] public void ContractsIsTheAuthoredDefaultTab() { // Opening on the wrong tab would look like the panel is empty. ElementInfo slot = Import( JournalPanelController.HostLayoutId, JournalPanelController.SlotElementId); UiPropertyValue tabs = Property(slot, TabTablePropertyId)!; UiPropertyValue defaultTab = Assert.Single( tabs.ArrayValue, t => t.StructValue.TryGetValue(TabDefaultPropertyId, out UiPropertyValue? d) && d.BoolValue); Assert.Equal( JournalPanelController.ContractsPageId, (uint)defaultTab.StructValue[TabPagePropertyId].UnsignedValue); } [Fact] public void TheToolbarButtonAuthorsTheSamePanelId() { // The button was already in ToolbarController.PanelButtonIds with no // panel behind it, so clicking it did nothing. This is the fact that // makes it work rather than a keybind. ElementInfo toolbar = Import(ToolbarLayoutId); ElementInfo? button = Find(toolbar, JournalToolbarButtonId); Assert.NotNull(button); UiPropertyValue? key = Property(button!, SlotKeyPropertyId); Assert.NotNull(key); Assert.Equal(RetailPanelCatalog.Journal, (uint)key!.UnsignedValue); } [Fact] public void TheContractsPageCarriesEveryChildTheControllerResolves() { // A renamed or missing child would silently leave a blank row rather // than fail, because the controller binds defensively. ElementInfo slot = Import( JournalPanelController.HostLayoutId, JournalPanelController.SlotElementId); ElementInfo page = Assert.IsType( Find(slot, JournalPanelController.ContractsPageId)); foreach (uint childId in new[] { 0x100005CFu, // the list 0x100005DEu, // description 0x100005DFu, // status value 0x100005E0u, // contact value 0x100005E1u, // contact location 0x100005E2u, // quest location 0x100005E3u, // timed value 0x100005DCu, // Abandon }) { Assert.True( Find(page, childId) is not null, $"contracts page is missing authored child 0x{childId:X8}"); } } private static ElementInfo? Find(ElementInfo root, uint id) { if (root.Id == id) return root; foreach (ElementInfo child in root.Children) { ElementInfo? hit = Find(child, id); if (hit is not null) return hit; } return null; } }