281 lines
12 KiB
C#
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();
|
|
}
|