acdream/tests/AcDream.App.Tests/UI/Layout/SpellbookWindowControllerTests.cs

281 lines
12 KiB
C#

using AcDream.App.Spells;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.Core.Items;
using AcDream.Core.Spells;
using System.Numerics;
namespace AcDream.App.Tests.UI.Layout;
public sealed class SpellbookWindowControllerTests
{
private static readonly SpellbookRowStyle RowStyle = new(
Width: 280f,
Height: 32f,
IconLeft: 0f,
IconTop: 0f,
IconWidth: 32f,
IconHeight: 32f,
LabelLeft: 42f,
LabelWidth: 230f,
FontDid: 0x40000001u,
LabelColor: Vector4.One,
BackgroundSprite: 0x06001396u,
SelectedSprite: 0x06001397u);
[Fact]
public void RetailFixture_BindsTextTabs_AndPropagatesOpenClosedState()
{
ImportedLayout layout = LayoutImporter.Build(
FixtureLoader.LoadSpellbookInfos(),
_ => (0u, 0, 0),
datFont: null,
stringResolve: value => value.StringId switch
{
49867858u => "Spellbook",
79360818u => "Components",
_ => null,
});
var closed = new List<uint>();
using SpellbookWindowController? controller = Bind(
layout, new Spellbook(), close: () => 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("Spellbook", Assert.Single(spellTab.LinesProvider()).Text);
Assert.Equal("Components", Assert.Single(componentTab.LinesProvider()).Text);
Assert.False(spellTab.OneLine);
Assert.True(spellTab.Centered);
Assert.Equal(VJustify.Center, spellTab.VerticalJustify);
Assert.True(spellTab.DrawTextAfterChildren);
Assert.True(componentTab.DrawTextAfterChildren);
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);
}
[Fact]
public void RetailFixture_BindsFilterIndicatorChildren_AndDeleteCaptionChild()
{
ImportedLayout layout = LayoutImporter.Build(
FixtureLoader.LoadSpellbookInfos(),
_ => (0u, 0, 0),
datFont: null,
stringResolve: value => value.StringId == 164868556u ? "Delete" : "Filter");
UiButton creature = Assert.IsType<UiButton>(layout.FindElement(0x10000298u));
Assert.Equal((0f, 1f, 13f, 13f),
(creature.FaceLeft, creature.FaceTop, creature.FaceWidth, creature.FaceHeight));
Assert.Equal(UiButton.LabelAlignment.Left, creature.LabelAlign);
Assert.Equal(17f, creature.LabelOffsetX);
Assert.Equal("Filter", creature.Label);
Assert.True(creature.ToggleBehavior);
creature.Selected = true;
Assert.Equal("Highlight", creature.ActiveState);
Assert.Equal(UiButtonStateMachine.Highlight, creature.ActiveRetailStateId);
UiButton levelSix = Assert.IsType<UiButton>(layout.FindElement(0x100002A1u));
Assert.Equal(UiButton.LabelAlignment.Left, levelSix.LabelAlign);
Assert.Equal(17f, levelSix.LabelOffsetX);
UiButton delete = Assert.IsType<UiButton>(
layout.FindElement(SpellbookWindowController.DeleteButtonId));
Assert.Equal("Delete", delete.Label);
Assert.Equal(UiButton.LabelAlignment.Center, delete.LabelAlign);
Assert.Equal((187f, 61f, 100f, 32f),
(delete.Left, delete.Top, delete.FaceWidth, delete.FaceHeight));
}
[Fact]
public void LearnedSpells_AreAuthoredRows_InDisplayOrder_WithSelectionAndDragPayload()
{
ImportedLayout layout = FixtureLoader.LoadSpellbook();
Spellbook book = CreateSpellbook();
book.OnSpellLearned(101u);
book.OnSpellLearned(102u);
book.OnSpellLearned(103u);
var added = new List<uint>();
using SpellbookWindowController controller = Bind(
layout, book, addFavorite: added.Add)!;
UiItemList list = Assert.IsType<UiItemList>(
layout.FindElement(SpellbookWindowController.SpellListId));
UiScrollbar scrollbar = Assert.IsType<UiScrollbar>(
layout.FindElement(SpellbookWindowController.SpellScrollbarId));
Assert.Equal(1, list.Columns);
Assert.Equal(280f, list.CellWidth);
Assert.Equal(32f, list.CellHeight);
Assert.Same(list.Scroll, scrollbar.Model);
Assert.Equal(3, list.GetNumUIItems());
UiCatalogSlot first = Assert.IsType<UiCatalogSlot>(list.GetItem(0));
UiCatalogSlot second = Assert.IsType<UiCatalogSlot>(list.GetItem(1));
UiCatalogSlot third = Assert.IsType<UiCatalogSlot>(list.GetItem(2));
Assert.Equal([102u, 101u, 103u], [first.EntryId, second.EntryId, third.EntryId]);
Assert.Equal("Creature One", first.Label);
Assert.True(first.ShowLabel);
Assert.Equal(RowStyle.BackgroundSprite, first.BackgroundSprite);
Assert.Equal(RowStyle.SelectedSprite, first.SelectedSprite);
Assert.True(first.SelectionBehindContent);
Assert.Equal(RowStyle.LabelLeft, first.LabelLeft);
second.OnEvent(new UiEvent(0, second, UiEventType.Click));
Assert.True(second.Selected);
Assert.False(first.Selected);
first.OnEvent(new UiEvent(
0, first, UiEventType.DragBegin,
Payload: new SpellbookShortcutDragPayload(first.EntryId)));
Assert.True(first.Selected);
Assert.IsType<SpellbookShortcutDragPayload>(first.GetDragPayload());
first.OnEvent(new UiEvent(0, first, UiEventType.DoubleClick));
Assert.Equal([102u], added);
}
[Fact]
public void SchoolAndLevelFilters_RebuildFromAuthoritativeBitfield()
{
ImportedLayout layout = FixtureLoader.LoadSpellbook();
Spellbook book = CreateSpellbook();
book.OnSpellLearned(101u); // War I
book.OnSpellLearned(102u); // Creature I
book.OnSpellLearned(103u); // War VIII
var sent = new List<uint>();
using SpellbookWindowController controller = Bind(
layout, book, sendFilter: sent.Add)!;
UiItemList list = Assert.IsType<UiItemList>(
layout.FindElement(SpellbookWindowController.SpellListId));
UiButton war = Assert.IsType<UiButton>(layout.FindElement(0x1000029Bu));
UiButton levelOne = Assert.IsType<UiButton>(layout.FindElement(0x1000029Cu));
Assert.True(war.Selected);
Assert.Equal(3, list.GetNumUIItems());
war.OnEvent(new UiEvent(0, war, UiEventType.Click));
controller.Tick();
Assert.Equal([0x3FF7u], sent);
Assert.Single(VisibleSpellIds(list));
Assert.Equal(102u, VisibleSpellIds(list)[0]);
war.OnEvent(new UiEvent(0, war, UiEventType.Click));
levelOne.OnEvent(new UiEvent(0, levelOne, UiEventType.Click));
controller.Tick();
Assert.Equal([103u], VisibleSpellIds(list));
Assert.False(levelOne.Selected);
}
[Fact]
public void Delete_UsesSharedConfirmation_AndWaitsForServerRemovalNotice()
{
ImportedLayout layout = FixtureLoader.LoadSpellbook();
Spellbook book = CreateSpellbook();
book.OnSpellLearned(101u);
var removed = new List<uint>();
string? prompt = null;
Action<bool>? complete = null;
using SpellbookWindowController controller = Bind(
layout,
book,
removeSpell: removed.Add,
showConfirmation: (message, callback) =>
{
prompt = message;
complete = callback;
})!;
UiItemList list = Assert.IsType<UiItemList>(
layout.FindElement(SpellbookWindowController.SpellListId));
UiButton delete = Assert.IsType<UiButton>(
layout.FindElement(SpellbookWindowController.DeleteButtonId));
delete.OnEvent(new UiEvent(0, delete, UiEventType.Click));
Assert.Null(prompt);
UiCatalogSlot row = Assert.IsType<UiCatalogSlot>(list.GetItem(0));
row.OnEvent(new UiEvent(0, row, UiEventType.Click));
delete.OnEvent(new UiEvent(0, delete, UiEventType.Click));
Assert.Equal(
"Are you sure you want to remove War One from your spellbook? "
+ "You will no longer be able to cast this spell unless you learn it again!",
prompt);
Assert.Empty(removed);
complete!(false);
Assert.Empty(removed);
Assert.True(book.Knows(101u));
delete.OnEvent(new UiEvent(0, delete, UiEventType.Click));
complete!(true);
Assert.Equal([101u], removed);
Assert.True(book.Knows(101u));
Assert.Single(VisibleSpellIds(list));
book.OnSpellForgotten(101u);
controller.Tick();
Assert.False(book.Knows(101u));
Assert.Empty(VisibleSpellIds(list));
}
private static SpellbookWindowController? Bind(
ImportedLayout layout,
Spellbook spellbook,
Action<uint>? addFavorite = null,
Action<uint>? sendFilter = null,
Action<uint>? removeSpell = null,
Action<string, Action<bool>>? showConfirmation = null,
Action? close = null)
=> SpellbookWindowController.Bind(
layout,
spellbook,
new ClientObjectTable(),
() => 1u,
new Dictionary<uint, SpellComponentDescriptor>(),
spellId => spellId + 1000u,
iconId => iconId,
spellId => spellId == 103u ? 8 : 1,
_ => { },
addFavorite ?? (_ => { }),
sendFilter ?? (_ => { }),
removeSpell ?? (_ => { }),
showConfirmation ?? ((_, _) => { }),
(_, _) => { },
close ?? (() => { }),
RowStyle,
rowFont: null);
private static Spellbook CreateSpellbook()
{
const string csv = "Spell ID,Name,School,IconId [Hex],SortKey\n"
+ "101,War One,War Magic,0x06000001,2\n"
+ "102,Creature One,Creature Enchantment,0x06000002,1\n"
+ "103,War Eight,War Magic,0x06000003,3\n";
return new Spellbook(SpellTable.LoadFromReader(new StringReader(csv)));
}
private static uint[] VisibleSpellIds(UiItemList list)
=> Enumerable.Range(0, list.GetNumUIItems())
.Select(index => Assert.IsType<UiCatalogSlot>(list.GetItem(index)).EntryId)
.ToArray();
}