acdream/tests/AcDream.App.Tests/Spells/SpellCastingControllerTests.cs
Erik 07be994d97 feat: port retail magic lifecycle and retained spell UI
Complete the retail cast-intent, target, component, enchantment, and busy-state paths; mount the DAT-authored spell bar, spellbook, component book, effects panels, and shared panel lifecycle; and add scoped input plus conformance coverage.

Co-Authored-By: Codex <noreply@openai.com>
2026-07-15 10:55:22 +02:00

119 lines
4.3 KiB
C#

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<string>();
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<InvalidOperationException>(() => 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<uint>? sendUntargeted = null,
Action<uint, uint>? sendTargeted = null,
Action<string>? display = null,
Func<uint, bool>? hasComponents = null,
Action? incrementBusy = null,
Func<bool>? 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;
}
}