using FluentAssertions; using Game.Blackjack; using OECS; using OECS.PlayTest; using Xunit; using Xunit.Abstractions; namespace Game.Blackjack.Tests; /// /// Baseline snapshot and log tests for Blackjack. /// Captures the world state as text and R3 change logs so they can be /// reviewed manually and compared across changes. /// public class SnapshotTests { private readonly ITestOutputHelper _output; public SnapshotTests(ITestOutputHelper output) { _output = output; } [Fact] public void Snapshot_InitialState() { var (world, _) = TestHelpers.SetupGame(); var snapshot = TestHelpers.SnapshotWorld(world); _output.WriteLine(snapshot); snapshot.Should().Contain("Phase: Betting"); snapshot.Should().Contain("Chips: 100"); snapshot.Should().Contain("Round: 1"); } [Fact] public void Snapshot_AfterDeal() { var (world, group) = TestHelpers.SetupGame(seed: 42); world.Commands.Enqueue(new PlaceBetCommand { Amount = 10 }); group.RunLogical(); var snapshot = TestHelpers.SnapshotWorld(world); _output.WriteLine(snapshot); snapshot.Should().Contain("Phase: PlayerTurn"); snapshot.Should().Contain("Chips: 90"); snapshot.Should().Contain("Bet: 10"); snapshot.Should().Contain("Player hand:"); snapshot.Should().Contain("Dealer hand:"); snapshot.Should().Contain("Cards in deck:"); } [Fact] public void Snapshot_AfterHit() { var (world, group) = TestHelpers.SetupGame(seed: 42); world.Commands.Enqueue(new PlaceBetCommand { Amount = 10 }); group.RunLogical(); world.Commands.Enqueue(new HitCommand()); group.RunLogical(); var snapshot = TestHelpers.SnapshotWorld(world); _output.WriteLine(snapshot); snapshot.Should().NotContain("Phase: Dealing"); } [Fact] public void Snapshot_AfterStand() { var (world, group) = TestHelpers.SetupGame(seed: 42); world.Commands.Enqueue(new PlaceBetCommand { Amount = 10 }); group.RunLogical(); world.Commands.Enqueue(new StandCommand()); group.RunLogical(); var snapshot = TestHelpers.SnapshotWorld(world); _output.WriteLine(snapshot); snapshot.Should().Contain("Phase: RoundOver"); snapshot.Should().Contain("Result:"); } [Fact] public void Log_ReactivityDuringRound() { var (world, group) = TestHelpers.SetupGame(seed: 42); using var capture = new ObservableCapture(world); capture.FormatWith(c => $"{c.Rank} of {c.Suit}"); capture.FormatWith(s => $"{s.Phase} Chips={s.Chips} Bet={s.CurrentBet} Round={s.RoundNumber}"); world.Commands.Enqueue(new PlaceBetCommand { Amount = 10 }); group.RunLogical(); world.Commands.Enqueue(new StandCommand()); group.RunLogical(); var logText = string.Join("\n", capture.GetLogLines()); _output.WriteLine(logText); capture.GetLogLines().Should().Contain(l => l.Contains("EntityAdded")); capture.GetLogLines().Should().Contain(l => l.Contains("ComponentAdded") && l.Contains("Card")); capture.GetLogLines().Should().Contain(l => l.Contains("ComponentModified") && l.Contains("GameState")); } [Fact] public void Serialization_RoundTrip_PreservesCoreState() { var (world, group) = TestHelpers.SetupGame(seed: 42); world.Commands.Enqueue(new PlaceBetCommand { Amount = 10 }); group.RunLogical(); var before = TestHelpers.SnapshotWorld(world); using var stream = new MemoryStream(); WorldSerializer.Save(world, stream); stream.Position = 0; var world2 = new World(); WorldSerializer.Load(world2, stream); var after = TestHelpers.SnapshotWorld(world2); _output.WriteLine("=== Before ==="); _output.WriteLine(before); _output.WriteLine("=== After ==="); _output.WriteLine(after); var state = world2.ReadSingleton(); state.Phase.Should().Be(GamePhase.PlayerTurn); state.Chips.Should().Be(90); state.CurrentBet.Should().Be(10); state.RoundNumber.Should().Be(1); after.Should().Contain("Card entities: 52"); } }