diff --git a/src/api/GameStateSchemaFilter.cs b/src/api/GameStateSchemaFilter.cs index 1ceb0772..0530e552 100644 --- a/src/api/GameStateSchemaFilter.cs +++ b/src/api/GameStateSchemaFilter.cs @@ -5,7 +5,7 @@ namespace UfoGameLib.Api; -// kja try to dedup the dependency of this filter on UfoGameLib.State.GameSession.StateJsonSerializerOptions. +// kja2-1 try to dedup the dependency of this filter on UfoGameLib.State.GameSession.StateJsonSerializerOptions. /// /// This schema filter ensures that OpenAPI schema generated from C# classes is generated properly. /// This means following transformations: diff --git a/src/game-lib-tests/GameSessionTests.cs b/src/game-lib-tests/GameSessionTests.cs index 1e7f4e8b..c4e5659f 100644 --- a/src/game-lib-tests/GameSessionTests.cs +++ b/src/game-lib-tests/GameSessionTests.cs @@ -121,13 +121,7 @@ public void LoadingPreviousGameStateOverridesCurrentState() GameStatePlayerView stateView = controller.GameStatePlayerView; int savedTurn = stateView.CurrentTurn; - // kja todo: add Clone() test that: - // Assert.That(!stateView.StateReferenceEquals(startingGameState)); - // Assert.That(startingGameState.IsEqualTo(session.CurrentGameState)); - // See VerifyGameSatesByJsonDiff - // See usage of clone in AddCurrentStateToPastStates - // See CurrentGameStateSerializedAsJsonString - GameState startingGameState = session.CurrentGameState.Clone(); + GameState initialGameState = session.CurrentGameState.Clone(); // Act 1: Save game controller.SaveGameState(); @@ -136,8 +130,8 @@ public void LoadingPreviousGameStateOverridesCurrentState() controller.AdvanceTime(); // kja move this into clone-specific test - // Assume that startingGameState has not been modified by advancing time. - Assert.That(startingGameState.Timeline.CurrentTurn, Is.EqualTo(savedTurn)); + // Assume that initialGameState has not been modified by advancing time. + Assert.That(initialGameState.Timeline.CurrentTurn, Is.EqualTo(savedTurn)); // Assert that advancing time didn't modify reference to current game state Assert.That(stateView.StateReferenceEquals(controller.GameStatePlayerView)); @@ -146,25 +140,12 @@ public void LoadingPreviousGameStateOverridesCurrentState() // Act 3: Load game GameState loadedGameState = controller.Load(); - - // kja implement IEquatable so this Is.EqualTo works. See TODO below. - // See VerifyGameSatesByJsonDiff Assert.That(loadedGameState, Is.EqualTo(session.CurrentGameState)); - // kja this will fail because since I updated to NUnit 4.0.0, the objects are considered equal, - // because NUnit 4.0.0 compares all public properties. See: - // https://github.com/nunit/nunit/pull/4436 - // https://github.com/nunit/nunit/issues/4394 - // Above are mentioned in: - // https://docs.nunit.org/articles/nunit/release-notes/framework.html#enhancements - // linked from: - // https://docs.nunit.org/articles/nunit/release-notes/breaking-changes.html#nunit-40 - // I triggered this bug by this change: - // https://github.com/konrad-jamrozik/game/commit/fa17b0985af7adde4f135be3d231555b6e7621ee#diff-718fb94a7176526686c9940ce6d3b5350e548e26a234b86a7cdd4817e68b3b52R10 - // kja should this be IsSameAs? - Assert.That(loadedGameState, Is.Not.EqualTo(startingGameState)); + + Assert.That(loadedGameState, Is.Not.EqualTo(initialGameState)); Assert.That(stateView.CurrentTurn, Is.EqualTo(savedTurn), "savedTurn"); Assert.That( - startingGameState, + initialGameState, Is.Not.EqualTo(loadedGameState), "starting state should not be equal to final state"); } diff --git a/src/game-lib-tests/GameStateTests.cs b/src/game-lib-tests/GameStateTests.cs new file mode 100644 index 00000000..3ad40d9e --- /dev/null +++ b/src/game-lib-tests/GameStateTests.cs @@ -0,0 +1,91 @@ +using UfoGameLib.State; + +namespace UfoGameLib.Tests; + +public class GameStateTests +{ + [SetUp] + public void Setup() + { + } + + /// + /// This test proves that: + /// - cloning of a game state works as expected + /// - equality of game states works as expected + /// - equality of player views works as expected + /// - equality of states referenced by player views works as expected + /// + /// Specifically: + /// + /// Given: + /// - An original game state, which is an arbitrary game state (here: initial game state)) + /// - A player view of it, and a second player view of it + /// - A clone of that game state + /// - A player view of the clone of that game state + /// - A modified clone of that game state + /// + /// The following holds: + /// - Game state player views retain references to game states they were created with + /// - A clone of game state is equal to the original (source of the cloned) game state + /// - A game state player view referencing a cloned game state is + /// equal to a player view of the original game state, because both these + /// views reference game states that are equal. + /// - A modified clone of a game state is not equal to the original game state + /// - A game state player view referencing a modified clone of a game state + /// is not equal to a player view of the original game state, because these + /// views reference game states that are not equal. + /// + [Test] + public void CloningAndViewAndEqualityBehaveCorrectly() + { + var originalGameState = GameState.NewInitialGameState(); + var originalGameStateView = new GameStatePlayerView(originalGameState); + var originalGameStateView2 = new GameStatePlayerView(originalGameState); + // Act: clone game state + var clonedState = originalGameState.Clone(); + var clonedStateView = new GameStatePlayerView(clonedState); + + Assert.Multiple(() => + { + // kja implement IEquatable so this Is.EqualTo works as expected. + // See VerifyGameStatesByJsonDiff + // See usage of clone in AddCurrentStateToPastStates + // See CurrentGameStateSerializedAsJsonString + // Act: exercise game state equality + Assert.That(clonedState, Is.EqualTo(originalGameState)); + + // Act: exercise game state view equality + Assert.That(clonedStateView, Is.EqualTo(originalGameStateView)); + + // Act: exercise game state view game state reference equality + Assert.That(originalGameStateView2.StateReferenceEquals(originalGameStateView)); + Assert.That(!originalGameStateView.StateReferenceEquals(clonedStateView)); + + // Arrange: modify the cloned game state + clonedState.Timeline.CurrentTurn += 1; + + /* + // kja this will fail because since I updated to NUnit 4.0.0, the objects are considered equal, + // kja should this be IsSameAs? + This assertion is necessary, in addition to: + + Assert.That(clonedState, Is.EqualTo(initialState)); + + Without properly implemented equality the "Is.EqualTo" assert would pass, + even though the objects are not equal. + This is because NUnit 4.0.0 compares all public properties. See: + https://github.com/nunit/nunit/pull/4436 + https://github.com/nunit/nunit/issues/4394 + Above are mentioned in: + https://docs.nunit.org/articles/nunit/release-notes/framework.html#enhancements + linked from: + https://docs.nunit.org/articles/nunit/release-notes/breaking-changes.html#nunit-40 + I originally migrated to this new behavior by updating NUnit to 4.0.0, in this commit: + https://github.com/konrad-jamrozik/game/commit/fa17b0985af7adde4f135be3d231555b6e7621ee#diff-718fb94a7176526686c9940ce6d3b5350e548e26a234b86a7cdd4817e68b3b52R10 + */ + Assert.That(clonedState, Is.Not.EqualTo(originalGameState)); + Assert.That(clonedStateView, Is.Not.EqualTo(originalGameStateView)); + }); + } +} \ No newline at end of file diff --git a/src/game-lib/State/GameStatePlayerView.cs b/src/game-lib/State/GameStatePlayerView.cs index 75e4001c..a7815fe5 100644 --- a/src/game-lib/State/GameStatePlayerView.cs +++ b/src/game-lib/State/GameStatePlayerView.cs @@ -2,24 +2,23 @@ namespace UfoGameLib.State; -public class GameStatePlayerView +public class GameStatePlayerView(GameState state) { - private readonly GameSession _session; + private readonly GameState _state = state; - public GameStatePlayerView(GameSession session) + public GameStatePlayerView(GameSession session) : this(session.CurrentGameState) { - _session = session; } - public int CurrentTurn => _session.CurrentGameState.Timeline.CurrentTurn; - public Missions Missions => _session.CurrentGameState.Missions; - public MissionSites MissionSites => _session.CurrentGameState.MissionSites; - public Assets Assets => _session.CurrentGameState.Assets; - public Agents TerminatedAgents => _session.CurrentGameState.TerminatedAgents; + public int CurrentTurn => _state.Timeline.CurrentTurn; + public Missions Missions => _state.Missions; + public MissionSites MissionSites => _state.MissionSites; + public Assets Assets => _state.Assets; + public Agents TerminatedAgents => _state.TerminatedAgents; public bool StateReferenceEquals(GameState state) - => ReferenceEquals(state, _session.CurrentGameState); + => ReferenceEquals(state, _state); public bool StateReferenceEquals(GameStatePlayerView view) - => ReferenceEquals(view._session.CurrentGameState, _session.CurrentGameState); + => ReferenceEquals(view._state, _state); } \ No newline at end of file