openac-mosswartmassacre/tests/OpenAC.MosswartMassacre.Tests/FlagTracker/FlagTrackerTests.cs
Erik 162d242c06 Meta sync: pull the club's metas and navs into the shared profiles
The original kept VTank's profile folder in step with the club's metas
repository; the port does the same against host.VtankProfiles, dropping
.met files under mosstank/metas and .nav files under mosstank/navs where
MossTank reads dropped profiles, flattened to their bare names as the
original flattened them. The Gitea tree and raw endpoints are the
original's; the repository address is the metas_repository_url setting,
defaulting to the original repository. An overwritten file keeps its old
copy as .bak (MossTank's pickers skip .bak side files).

Storage is text-only, so the SHA-256 compare hashes the text a stored read
gives back (UTF-8, BOM dropped); for the plain-text files in the repo that
equals the original's byte compare. Network work runs on the thread pool,
one operation after another; chat lines and window updates are applied on
the tick. The four /mm verbs keep their chat lines, and the Metas tab is
its own markup window with IsVisible for the main window to open.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 12:26:14 +02:00

239 lines
9.9 KiB
C#

using AcDream.Plugin.Abstractions;
using OpenAC.MosswartMassacre.FlagTracker;
using OpenAC.MosswartMassacre.Tests.Fakes;
namespace OpenAC.MosswartMassacre.Tests.FlagTracker;
public sealed class FlagTrackerTests
{
private const uint Player = 0x50000001;
private static PluginHarness WithUi(Action<FakeHost>? arrange = null) =>
new(arrange: host =>
{
host.HasUi = true;
arrange?.Invoke(host);
});
private static void SetPlayerInts(FakeHost host, Dictionary<uint, int> ints) =>
host.Automation.Objects.Properties[Player] = new PluginItemProperties(
ints,
new Dictionary<uint, long>(),
new Dictionary<uint, bool>(),
new Dictionary<uint, double>(),
new Dictionary<uint, string>(),
new Dictionary<uint, uint>(),
new Dictionary<uint, uint>());
private static FlagTrackerViewModel Panel(PluginHarness harness) =>
harness.Feature<FlagTrackerFeature>().Panel!;
private static int Row(IReadOnlyList<string> names, string name)
{
int index = names.ToList().IndexOf(name);
Assert.True(index >= 0, $"no row {name}");
return index;
}
[Fact]
public void A_headless_host_gets_no_window_and_login_does_nothing()
{
using var harness = new PluginHarness();
harness.Login();
harness.Run(6);
FlagTrackerFeature feature = harness.Feature<FlagTrackerFeature>();
Assert.Null(feature.Panel);
Assert.Empty(harness.Host.Ui.Panels);
}
[Fact]
public void A_graphical_host_gets_the_mossy_tracker_window_closed()
{
using var harness = WithUi();
RegisteredPanel panel = Assert.Single(harness.Host.Ui.Panels, registered => registered.Descriptor.WindowId == "flagtracker");
Assert.Equal("flagtracker", panel.Descriptor.WindowId);
Assert.Equal("Mossy Tracker", panel.Descriptor.Title);
Assert.True(panel.Descriptor.ShowInSidePanel);
Assert.EndsWith(FlagTrackerFeature.MarkupFile, panel.MarkupPath, StringComparison.Ordinal);
Assert.Same(Panel(harness), panel.Binding);
Assert.False(Panel(harness).IsVisible);
Assert.True(Panel(harness).AugsSelected);
Panel(harness).ShowQuests();
Assert.True(Panel(harness).QuestsSelected);
Assert.False(Panel(harness).AugsSelected);
}
[Fact]
public void Augmentations_come_from_the_players_own_properties()
{
using var harness = WithUi(host => SetPlayerInts(host, new() { [231] = 3, [230] = 2 }));
harness.Login();
FlagTrackerViewModel panel = Panel(harness);
Assert.Equal("--- Death Augs ---", panel.AugNames[0]);
int keepItems = Row(panel.AugNames, "Keep Items");
Assert.Equal("[MAX]", panel.AugProgress[keepItems]);
Assert.Equal(0x008000u, panel.AugProgressColors[keepItems]);
Assert.Equal("Rohula bint Ludun", panel.AugTrainers[keepItems]);
Assert.Equal("Ayan Baqur", panel.AugLocations[keepItems]);
int keepSpells = Row(panel.AugNames, "Keep Spells");
Assert.Equal("[0/1]", panel.AugProgress[keepSpells]);
Assert.Equal(0xFF0000u, panel.AugProgressColors[keepSpells]);
int carrying = Row(panel.AugNames, "Extra Carrying Capacity");
Assert.Equal("[2/5]", panel.AugProgress[carrying]);
Assert.Equal(0xFFFF00u, panel.AugProgressColors[carrying]);
// A bought augmentation shows after Refresh.
SetPlayerInts(harness.Host, new() { [232] = 1 });
panel.RefreshAugs();
Assert.Equal("[MAX]", panel.AugProgress[keepSpells]);
}
[Fact]
public void Luminance_auras_show_progress_and_the_seer_they_belong_to()
{
using var harness = WithUi(host => SetPlayerInts(host, new() { [333] = 5, [344] = 2 }));
harness.Login();
FlagTrackerViewModel panel = Panel(harness);
int damage = Row(panel.LumNames, "+1 Damage Rating");
Assert.Equal("5/5", panel.LumProgress[damage]);
Assert.Equal(0x008000u, panel.LumProgressColors[damage]);
Assert.Equal("Nalicana Auras", panel.LumCategories[damage]);
int kahiri = Row(panel.LumNames, "(Ka'hiri) +2 Specialized Skills");
Assert.Equal("2/5", panel.LumProgress[kahiri]);
Assert.Equal(0xFFFF00u, panel.LumProgressColors[kahiri]);
Assert.Equal("LoyalToKahiri", panel.LumCategories[kahiri]);
}
[Fact]
public void Recalls_show_known_and_unknown_with_their_spell_icons()
{
using var harness = WithUi(host => host.Automation.Spells.KnownSpellIds.Add(2023));
harness.Login();
FlagTrackerViewModel panel = Panel(harness);
Assert.Equal(17, panel.RecallNames.Count);
Assert.Equal("Recall the Sanctuary", panel.RecallNames[0]);
Assert.Equal(2023u, panel.RecallSpellIds[0]);
Assert.Equal("Known", panel.RecallStatus[0]);
Assert.Equal(0x008000u, panel.RecallStatusColors[0]);
Assert.Equal("Unknown", panel.RecallStatus[1]);
Assert.Equal(0xFF0000u, panel.RecallStatusColors[1]);
}
[Fact]
public void Cantrips_are_matched_to_trained_skills_attributes_and_wards()
{
using var harness = WithUi(host =>
{
host.Automation.Character.Skills.Add(new PluginSkillInfo(34, "War Magic", PluginSkillTraining.Specialized, 400));
host.Automation.Character.Skills.Add(new PluginSkillInfo(6, "Melee Defense", PluginSkillTraining.Trained, 300));
host.Automation.Character.Skills.Add(new PluginSkillInfo(21, "Healing", PluginSkillTraining.Untrained, 10));
host.Automation.Spells.All =
[
FakeSpellCatalog.Spell(4715, "Epic War Magic Aptitude"),
FakeSpellCatalog.Spell(2248, "Major Invulnerability"),
FakeSpellCatalog.Spell(1, "Minor Invulnerability"),
FakeSpellCatalog.Spell(6099, "Legendary Flame Ward"),
FakeSpellCatalog.Spell(2091, "Major Strength"),
FakeSpellCatalog.Spell(2, "Strength Self VIII"),
];
host.Automation.Character.ActiveEnchantments =
[
new PluginActiveEnchantment(4715, 0, 0, -1),
new PluginActiveEnchantment(2248, 0, 0, -1),
new PluginActiveEnchantment(1, 0, 0, -1),
new PluginActiveEnchantment(6099, 0, 0, -1),
new PluginActiveEnchantment(2091, 0, 0, -1),
new PluginActiveEnchantment(2, 0, 0, 3600),
];
});
harness.Login();
FlagTrackerViewModel panel = Panel(harness);
Assert.Equal(
[
"--- Specialized Skills ---", "War Magic",
"--- Trained Skills ---", "Invulnerability",
"--- Attributes ---", "Strength",
"--- Protection Auras ---", "Armor", "Bludgeoning Ward", "Piercing Ward",
"Slashing Ward", "Flame Ward", "Frost Ward", "Acid Ward", "Storm Ward",
],
panel.CantripNames);
Assert.Equal(FlagTrackerData.Icons.CategoryStar, panel.CantripIcons[0]);
Assert.Equal("Epic", panel.CantripLevels[1]);
Assert.Equal(0x800080u, panel.CantripLevelColors[1]);
Assert.Equal(FlagTrackerData.Icons.GreenCircle, panel.CantripIcons[1]);
// The higher of two cantrips on one skill wins.
Assert.Equal("Major", panel.CantripLevels[3]);
Assert.Equal(0x0000FFu, panel.CantripLevelColors[3]);
Assert.Equal("Major", panel.CantripLevels[5]);
Assert.Equal("N/A", panel.CantripLevels[7]);
Assert.Equal(FlagTrackerData.Icons.RedCircle, panel.CantripIcons[7]);
int flame = Row(panel.CantripNames, "Flame Ward");
Assert.Equal("Legendary", panel.CantripLevels[flame]);
Assert.Equal(0xFFA500u, panel.CantripLevelColors[flame]);
}
[Fact]
public void Tracked_weapons_are_found_in_the_inventory_by_name()
{
using var harness = WithUi(host => host.Automation.Items.Owned.Add(
FakeItems.Item(0x80000001, "Atlan Sword") with { IconId = 0x06001234 }));
harness.Login();
FlagTrackerViewModel panel = Panel(harness);
int atlan = Row(panel.WeaponNames, "Atlan Sword");
Assert.Equal("Acquired", panel.WeaponStatus[atlan]);
Assert.Equal(0x06001234u, panel.WeaponIcons[atlan]);
int axe = Row(panel.WeaponNames, "Atlan Axe");
Assert.Equal("Not Acquired", panel.WeaponStatus[axe]);
Assert.Equal(FlagTrackerData.Icons.Portal, panel.WeaponIcons[axe]);
}
[Fact]
public void The_quest_page_hides_maxed_quests_and_sorts_by_solves()
{
using var harness = WithUi();
harness.Login();
long now = new DateTimeOffset(harness.Clock.UtcNow).ToUnixTimeSeconds();
var chat = harness.Host.Automation.Chat;
chat.Deliver($"stipendtimer_0812 - 2 solves ({now - 10})\"x\" -1 3700");
chat.Deliver($"maxed - 5 solves ({now})\"x\" 5 60");
chat.Deliver($"aaa - 7 solves ({now - 100})\"x\" -1 50");
FlagTrackerViewModel panel = Panel(harness);
Assert.Equal(["aaa", "Stipend Collection Timer"], panel.QuestRowNames);
Assert.Equal(["READY", "1h 01m"], panel.QuestCountdowns);
Assert.Equal([0x008000u, 0xFFFF00u], panel.QuestCountdownColors);
Assert.Equal(["50s", "1h 1m 40s"], panel.QuestCooldowns);
Assert.Equal(["7", "2"], panel.QuestSolves);
// The countdowns move on every five seconds.
harness.Run(5.1);
Assert.Equal("1h 01m", panel.QuestCountdowns[1]);
harness.Run(120);
Assert.StartsWith("59m ", panel.QuestCountdowns[1], StringComparison.Ordinal);
}
[Fact]
public void Logoff_empties_the_pages()
{
using var harness = WithUi();
harness.Login();
Assert.NotEmpty(Panel(harness).AugNames);
harness.Plugin.OnLogoff();
Assert.Empty(Panel(harness).AugNames);
Assert.Empty(Panel(harness).QuestRowNames);
}
}