using FluentAssertions; using Game.TicTacToe; using OECS; using OECS.PlayTest; using Xunit; using Xunit.Abstractions; namespace Game.TicTacToe.Tests; /// /// Baseline snapshot and log tests for TicTacToe. /// 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_InitialBoard() { var world = new World(); for (int r = 0; r < 3; r++) for (int c = 0; c < 3; c++) world.AddComponent(world.CreateEntity(), new Cell { Row = r, Col = c }); world.SetSingleton(new GameState { CurrentPlayer = Player.X, Status = GameStatus.Playing, MoveCount = 0 }); var snapshot = TestHelpers.SnapshotWorld(world); _output.WriteLine(snapshot); snapshot.Should().Contain("GameState: X's turn, 0 moves"); snapshot.Should().Contain("Cells: 9 total, 0 marked"); } [Fact] public void Snapshot_AfterThreeMoves() { var (world, group) = TestHelpers.SetupGame(); TestHelpers.PlayMoves(world, group, (0, 0), (1, 1), (2, 2)); var snapshot = TestHelpers.SnapshotWorld(world); _output.WriteLine(snapshot); snapshot.Should().Contain("GameState: O's turn, 3 moves"); snapshot.Should().Contain("Cells: 9 total, 3 marked"); snapshot.Should().Contain("(0,0): X"); snapshot.Should().Contain("(1,1): O"); snapshot.Should().Contain("(2,2): X"); } [Fact] public void Snapshot_XWins() { var (world, group) = TestHelpers.SetupGame(); TestHelpers.PlayMoves(world, group, (0, 0), (1, 0), (0, 1), (1, 1), (0, 2)); var snapshot = TestHelpers.SnapshotWorld(world); _output.WriteLine(snapshot); snapshot.Should().Contain("GameState: XWon"); snapshot.Should().Contain("Cells: 9 total, 5 marked"); } [Fact] public void Snapshot_Draw() { var (world, group) = TestHelpers.SetupGame(); TestHelpers.PlayMoves(world, group, (0, 0), (0, 1), (0, 2), (1, 1), (1, 0), (1, 2), (2, 1), (2, 0), (2, 2)); var snapshot = TestHelpers.SnapshotWorld(world); _output.WriteLine(snapshot); snapshot.Should().Contain("GameState: Draw"); snapshot.Should().Contain("Cells: 9 total, 9 marked"); } [Fact] public void Log_ReactivityDuringGame() { var (world, group) = TestHelpers.SetupGame(); using var capture = new ObservableCapture(world); capture.FormatWith(m => m.Player.ToString()); capture.FormatWith(s => $"{s.Status} ({s.CurrentPlayer}, {s.MoveCount} moves)"); TestHelpers.PlayMoves(world, group, (0, 0), (1, 0), (0, 1), (1, 1), (0, 2)); var logText = string.Join("\n", capture.GetLogLines()); _output.WriteLine(logText); capture.GetLogLines().Should().Contain(l => l.Contains("ComponentAdded") && l.Contains("Mark")); capture.GetLogLines().Should().Contain(l => l.Contains("ComponentModified") && l.Contains("GameState")); } [Fact] public void Serialization_RoundTrip_PreservesSnapshot() { var (world, group) = TestHelpers.SetupGame(); TestHelpers.PlayMoves(world, group, (0, 0), (1, 1), (2, 2)); 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); after.Should().Be(before); } }