fix(ui): restore retail spell tabs and spellbook

Bind the spellbook and component-book tabs as their authored stateful text controls, propagate Closed/Open state into the retained DAT child tree, and keep authored label colors live across state changes.

Match retail UIElement_Text zero-margin construction so the Magic favorite captions I through VIII are no longer clipped. Add a real portal.dat spellbook fixture plus controller, state-propagation, and text-layout regression coverage.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-15 15:54:11 +02:00
parent 07be994d97
commit ad30c37a48
15 changed files with 13492 additions and 38 deletions

View file

@ -0,0 +1,56 @@
using AcDream.App.Spells;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.Core.Items;
using AcDream.Core.Spells;
namespace AcDream.App.Tests.UI.Layout;
public sealed class SpellbookWindowControllerTests
{
[Fact]
public void RetailFixture_BindsTextTabs_AndPropagatesOpenClosedState()
{
ImportedLayout layout = FixtureLoader.LoadSpellbook();
var closed = new List<uint>();
using SpellbookWindowController? controller = SpellbookWindowController.Bind(
layout,
new Spellbook(),
new ClientObjectTable(),
() => 1u,
new Dictionary<uint, SpellComponentDescriptor>(),
spellId => spellId,
iconId => iconId,
_ => 1,
_ => { },
_ => { },
_ => { },
(_, _) => { },
() => closed.Add(1u));
Assert.NotNull(controller);
UiText spellTab = Assert.IsType<UiText>(layout.FindElement(SpellbookWindowController.SpellTabId));
UiText componentTab = Assert.IsType<UiText>(layout.FindElement(SpellbookWindowController.ComponentTabId));
UiElement spellPage = Assert.IsAssignableFrom<UiElement>(layout.FindElement(SpellbookWindowController.SpellPageId));
UiElement componentPage = Assert.IsAssignableFrom<UiElement>(layout.FindElement(SpellbookWindowController.ComponentPageId));
Assert.Equal(RetailUiStateIds.Open, spellTab.ActiveRetailStateId);
Assert.Equal(RetailUiStateIds.Closed, componentTab.ActiveRetailStateId);
Assert.True(spellPage.Visible);
Assert.False(componentPage.Visible);
Assert.Equal(3, spellTab.Children.Count);
Assert.All(spellTab.Children, child =>
Assert.Equal(RetailUiStateIds.Open, Assert.IsAssignableFrom<IUiDatStateful>(child).ActiveRetailStateId));
componentTab.OnEvent(new UiEvent(0, componentTab, UiEventType.Click));
Assert.Equal(SpellbookWindowPage.Components, controller!.CurrentPage);
Assert.Equal(RetailUiStateIds.Closed, spellTab.ActiveRetailStateId);
Assert.Equal(RetailUiStateIds.Open, componentTab.ActiveRetailStateId);
Assert.False(spellPage.Visible);
Assert.True(componentPage.Visible);
Assert.All(componentTab.Children, child =>
Assert.Equal(RetailUiStateIds.Open, Assert.IsAssignableFrom<IUiDatStateful>(child).ActiveRetailStateId));
Assert.Empty(closed);
}
}