using AcDream.App.Spells; using AcDream.Core.Spells; namespace AcDream.App.Tests.Spells; public sealed class SpellCastingControllerTests { [Fact] public void Cast_SelfTargeted_SendsLocalPlayerAsTarget() { Spellbook book = MakeBook(flags: 0x8, untargeted: true, targetMask: 0x10); uint target = 0; var controller = MakeController(book, selected: 99, player: 42, sendTargeted: (id, _) => target = id); Assert.Equal(CastRequestResult.Sent, controller.Cast(1)); Assert.Equal(42u, target); } [Fact] public void Cast_TargetedWithoutSelection_DoesNotSend() { Spellbook book = MakeBook(flags: 0, untargeted: false, targetMask: 0x10); int sends = 0; string message = ""; var controller = MakeController(book, selected: null, player: 42, sendTargeted: (_, _) => sends++, display: value => message = value); Assert.Equal(CastRequestResult.NoTarget, controller.Cast(1)); Assert.Equal(0, sends); Assert.Contains("select", message, StringComparison.OrdinalIgnoreCase); } [Fact] public void Cast_Untargeted_StopsThenSendsThenIncrementsBusy() { Spellbook book = MakeBook(flags: 0, untargeted: true, targetMask: 0); var order = new List(); var controller = MakeController(book, selected: null, player: 42, stop: () => order.Add("stop"), sendUntargeted: _ => order.Add("send"), incrementBusy: () => order.Add("busy")); Assert.Equal(CastRequestResult.Sent, controller.Cast(1)); Assert.Equal(["stop", "send", "busy"], order); } [Fact] public void Cast_MissingComponents_DoesNotIncrementBusyOrSend() { Spellbook book = MakeBook(flags: 0, untargeted: true, targetMask: 0); int sends = 0, busy = 0; var controller = MakeController(book, selected: null, player: 42, sendUntargeted: _ => sends++, hasComponents: _ => false, incrementBusy: () => busy++); Assert.Equal(CastRequestResult.MissingComponents, controller.Cast(1)); Assert.Equal(0, sends); Assert.Equal(0, busy); } [Fact] public void Cast_Disconnected_DoesNotIncrementBusy() { Spellbook book = MakeBook(flags: 0, untargeted: true, targetMask: 0); int busy = 0; var controller = MakeController(book, selected: null, player: 42, incrementBusy: () => busy++, canSend: () => false); Assert.Equal(CastRequestResult.Unavailable, controller.Cast(1)); Assert.Equal(0, busy); } [Fact] public void Cast_SendThrows_DoesNotIncrementBusyAndRethrows() { Spellbook book = MakeBook(flags: 0, untargeted: true, targetMask: 0); int increments = 0; var controller = MakeController(book, selected: null, player: 42, sendUntargeted: _ => throw new InvalidOperationException("wire"), incrementBusy: () => increments++); Assert.Throws(() => controller.Cast(1)); Assert.Equal(0, increments); Assert.Null(controller.LastRequestedSpellId); } private static SpellCastingController MakeController( Spellbook book, uint? selected, uint player, Action? stop = null, Action? sendUntargeted = null, Action? sendTargeted = null, Action? display = null, Func? hasComponents = null, Action? incrementBusy = null, Func? canSend = null) => new( book, () => selected, () => player, stop ?? (() => { }), sendUntargeted ?? (_ => { }), sendTargeted ?? ((_, _) => { }), display ?? (_ => { }), hasRequiredComponents: hasComponents, incrementBusy: incrementBusy, canSend: canSend); private static Spellbook MakeBook(uint flags, bool untargeted, uint targetMask) { const string header = "Spell ID,Name,Flags [Hex],IsUntargetted,TargetMask [Hex]"; string row = $"1,Test,0x{flags:X},{untargeted},0x{targetMask:X}"; SpellTable table = SpellTable.LoadFromReader(new StringReader($"{header}\n{row}")); var book = new Spellbook(table); book.OnSpellLearned(1); return book; } }