using KoogleApp.Components.Controls; using KoogleApp.Games.Training; using KoogleApp.Store.Game.ThrowPanel; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using FluentAssertions; namespace Koogle.Tests.ReducerTests { public class ThrowPanelStateTests { [Fact] public void StartStopAction_StartAGame() { // Arrange var initialState = new ThrowPanelState(); var action = new StartStopAction(new TrainingSetupState()); // Act var newState = ThrowPanelStateReducer.OnStartStop(initialState, action); // Assert newState.IsStated.Should().Be(true); } [Fact] public void StartStopAction_StopAGame() { // Arrange var initialState = new ThrowPanelState(); var action = new StartStopAction(null); // Act var newState = ThrowPanelStateReducer.OnStartStop(initialState, action); // Assert newState.IsStated.Should().Be(false); } [Theory] [InlineData(1, true, PinStatus.Fallen)] [InlineData(1, false, PinStatus.Standing)] [InlineData(5, true, PinStatus.Fallen)] [InlineData(9, true, PinStatus.Fallen)] public void TogglePinValueAction_SetsPinState(int pinNumber, bool isHit, PinStatus expectedStatus) { // Arrange var initialState = new ThrowPanelState(); var action = new TogglePinValueAction(isHit, pinNumber); // Act var newState = ThrowPanelStateReducer.OnTogglePinValue(initialState, action); // Assert var actualStatus = pinNumber switch { 1 => newState.Pin1State, 2 => newState.Pin2State, 3 => newState.Pin3State, 4 => newState.Pin4State, 5 => newState.Pin5State, 6 => newState.Pin6State, 7 => newState.Pin7State, 8 => newState.Pin8State, 9 => newState.Pin9State, _ => throw new ArgumentException("Invalid pin number") }; actualStatus.Should().Be(expectedStatus); newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.BeforeThrow); } [Fact] public void ToggleAllPinsAction_TogglesAllStandingPinsToFallen() { // Arrange var initialState = new ThrowPanelState(); // All pins standing // Act var newState = ThrowPanelStateReducer.OnToggleAllPins(initialState, new ToggleAllPinsAction()); // Assert newState.Pin1State.Should().Be(PinStatus.Fallen); newState.Pin2State.Should().Be(PinStatus.Fallen); newState.Pin3State.Should().Be(PinStatus.Fallen); newState.Pin4State.Should().Be(PinStatus.Fallen); newState.Pin5State.Should().Be(PinStatus.Fallen); newState.Pin6State.Should().Be(PinStatus.Fallen); newState.Pin7State.Should().Be(PinStatus.Fallen); newState.Pin8State.Should().Be(PinStatus.Fallen); newState.Pin9State.Should().Be(PinStatus.Fallen); newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.BeforeThrow); } [Fact] public void ToggleAllPinsAction_TogglesAllFallenPinsToStanding() { // Arrange var initialState = new ThrowPanelState() with { Pin1State = PinStatus.Fallen, Pin2State = PinStatus.Fallen, Pin3State = PinStatus.Fallen, Pin4State = PinStatus.Fallen, Pin5State = PinStatus.Fallen, Pin6State = PinStatus.Fallen, Pin7State = PinStatus.Fallen, Pin8State = PinStatus.Fallen, Pin9State = PinStatus.Fallen }; // Act var newState = ThrowPanelStateReducer.OnToggleAllPins(initialState, new ToggleAllPinsAction()); // Assert newState.Pin1State.Should().Be(PinStatus.Standing); newState.Pin2State.Should().Be(PinStatus.Standing); newState.Pin3State.Should().Be(PinStatus.Standing); newState.Pin4State.Should().Be(PinStatus.Standing); newState.Pin5State.Should().Be(PinStatus.Standing); newState.Pin6State.Should().Be(PinStatus.Standing); newState.Pin7State.Should().Be(PinStatus.Standing); newState.Pin8State.Should().Be(PinStatus.Standing); newState.Pin9State.Should().Be(PinStatus.Standing); } [Fact] public void ToggleAllPinsAction_DoesNotToggleDisabledPins() { // Arrange var initialState = new ThrowPanelState() with { Pin1State = PinStatus.Disabled, Pin2State = PinStatus.Standing, Pin3State = PinStatus.Standing }; // Act var newState = ThrowPanelStateReducer.OnToggleAllPins(initialState, new ToggleAllPinsAction()); // Assert newState.Pin1State.Should().Be(PinStatus.Disabled); newState.Pin2State.Should().Be(PinStatus.Fallen); newState.Pin3State.Should().Be(PinStatus.Fallen); } [Fact] public void ToggleBellAction_TogglesBellValue() { // Arrange var initialState = new ThrowPanelState(); // BellValue = false // Act var newState = ThrowPanelStateReducer.OnToggleBell(initialState, new ToggleBellAction()); // Assert newState.BellValue.Should().Be(true); newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.BeforeThrow); } [Fact] public void ToggleBellAction_TogglesBellValueBackToFalse() { // Arrange var initialState = new ThrowPanelState() with { BellValue = true }; // Act var newState = ThrowPanelStateReducer.OnToggleBell(initialState, new ToggleBellAction()); // Assert newState.BellValue.Should().Be(false); } [Fact] public void UpdatePinStateByNumberAction_SetFirstNPinsToFallen() { // Arrange var initialState = new ThrowPanelState(); // All pins standing var action = new UpdatePinStateByNumberAction(3); // Act var newState = ThrowPanelStateReducer.OnUpdatePinStateByNumber(initialState, action); // Assert - pins are set from the back (Pin9, Pin8, Pin7...) newState.Pin9State.Should().Be(PinStatus.Standing); newState.Pin8State.Should().Be(PinStatus.Standing); newState.Pin7State.Should().Be(PinStatus.Standing); newState.Pin6State.Should().Be(PinStatus.Standing); newState.Pin5State.Should().Be(PinStatus.Standing); newState.Pin4State.Should().Be(PinStatus.Standing); newState.Pin3State.Should().Be(PinStatus.Fallen); newState.Pin2State.Should().Be(PinStatus.Fallen); newState.Pin1State.Should().Be(PinStatus.Fallen); newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.BeforeThrow); } [Fact] public void UpdatePinStateByNumberAction_SetAllPinsToFallen() { // Arrange var initialState = new ThrowPanelState(); var action = new UpdatePinStateByNumberAction(9); // Act var newState = ThrowPanelStateReducer.OnUpdatePinStateByNumber(initialState, action); // Assert newState.Pin1State.Should().Be(PinStatus.Fallen); newState.Pin2State.Should().Be(PinStatus.Fallen); newState.Pin3State.Should().Be(PinStatus.Fallen); newState.Pin4State.Should().Be(PinStatus.Fallen); newState.Pin5State.Should().Be(PinStatus.Fallen); newState.Pin6State.Should().Be(PinStatus.Fallen); newState.Pin7State.Should().Be(PinStatus.Fallen); newState.Pin8State.Should().Be(PinStatus.Fallen); newState.Pin9State.Should().Be(PinStatus.Fallen); } [Fact] public void UpdatePinStateByNumberAction_SetZeroPinsToFallen() { // Arrange var initialState = new ThrowPanelState() with { Pin1State = PinStatus.Fallen, Pin2State = PinStatus.Fallen }; var action = new UpdatePinStateByNumberAction(0); // Act var newState = ThrowPanelStateReducer.OnUpdatePinStateByNumber(initialState, action); // Assert newState.Pin1State.Should().Be(PinStatus.Standing); newState.Pin2State.Should().Be(PinStatus.Standing); newState.Pin3State.Should().Be(PinStatus.Standing); } [Fact] public void EnsureBeforeThrowStatusAction_SetsStatusToBeforeThrow() { // Arrange var initialState = new ThrowPanelState() with { ThrowPanelStateStatus = ThrowPanelStateStatus.AfterThrow }; var action = new EnsureBeforeThrowStatusAction(null!, null!); // Act var newState = ThrowPanelStateReducer.OnEnsureBeforeThrowStatusAction(initialState, action); // Assert newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.BeforeThrow); } #region CalculateNextPanelState Extension Tests [Fact] public void CalculateNextPanelState_RepositionMode_ResetsPinsAndIncrementsCounter() { // Arrange var state = new ThrowPanelState() with { ThrowMode = ThrowMode.Reposition, ThrowsPerRound = 3, ThrowCounterPerRound = 1, ThrowCounter = 0, Pin1State = PinStatus.Fallen, Pin2State = PinStatus.Fallen, Pin3State = PinStatus.Standing, BellValue = true }; // Act var newState = state.CalculateNextPanelState(); // Assert - In Reposition mode, pins are reset after each throw newState.Pin1State.Should().Be(PinStatus.Standing); newState.Pin2State.Should().Be(PinStatus.Standing); newState.Pin3State.Should().Be(PinStatus.Standing); newState.ThrowCounterPerRound.Should().Be(2); newState.ThrowCounter.Should().Be(1); newState.BellValue.Should().Be(false); newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.AfterThrow); } [Fact] public void CalculateNextPanelState_DecreaseMode_DisablesFallenPins() { // Arrange var state = new ThrowPanelState() with { ThrowMode = ThrowMode.Decrease, ThrowsPerRound = 3, ThrowCounterPerRound = 1, ThrowCounter = 0, Pin1State = PinStatus.Fallen, Pin2State = PinStatus.Fallen, Pin3State = PinStatus.Standing, Pin4State = PinStatus.Standing, Pin5State = PinStatus.Standing, Pin6State = PinStatus.Standing, Pin7State = PinStatus.Standing, Pin8State = PinStatus.Standing, Pin9State = PinStatus.Standing }; // Act var newState = state.CalculateNextPanelState(); // Assert - In Decrease mode, fallen pins become disabled newState.Pin1State.Should().Be(PinStatus.Disabled); newState.Pin2State.Should().Be(PinStatus.Disabled); newState.Pin3State.Should().Be(PinStatus.Standing); newState.ThrowCounterPerRound.Should().Be(2); } [Fact] public void CalculateNextPanelState_AllPinsFallen_ResetsPinsRegardlessOfMode() { // Arrange - All pins fallen (cleared) var state = new ThrowPanelState() with { ThrowMode = ThrowMode.Decrease, ThrowsPerRound = 3, ThrowCounterPerRound = 1, Pin1State = PinStatus.Fallen, Pin2State = PinStatus.Fallen, Pin3State = PinStatus.Fallen, Pin4State = PinStatus.Fallen, Pin5State = PinStatus.Fallen, Pin6State = PinStatus.Fallen, Pin7State = PinStatus.Fallen, Pin8State = PinStatus.Fallen, Pin9State = PinStatus.Fallen }; // Act var newState = state.CalculateNextPanelState(); // Assert - All pins reset when cleared newState.Pin1State.Should().Be(PinStatus.Standing); newState.Pin2State.Should().Be(PinStatus.Standing); newState.Pin3State.Should().Be(PinStatus.Standing); newState.Pin4State.Should().Be(PinStatus.Standing); newState.Pin5State.Should().Be(PinStatus.Standing); newState.Pin6State.Should().Be(PinStatus.Standing); newState.Pin7State.Should().Be(PinStatus.Standing); newState.Pin8State.Should().Be(PinStatus.Standing); newState.Pin9State.Should().Be(PinStatus.Standing); } [Fact] public void CalculateNextPanelState_LastThrowInRound_ResetsCounterToOne() { // Arrange var state = new ThrowPanelState() with { ThrowMode = ThrowMode.Reposition, ThrowsPerRound = 3, ThrowCounterPerRound = 3, // Last throw of round ThrowCounter = 2 }; // Act var newState = state.CalculateNextPanelState(); // Assert newState.ThrowCounterPerRound.Should().Be(1); newState.ThrowCounter.Should().Be(3); } #endregion #region Reducer State Replacement Tests [Fact] public void OnReceiveStateFromServer_ReplacesEntireState() { // Arrange var initialState = new ThrowPanelState(); var serverState = new ThrowPanelState() with { IsStated = true, ThrowCounter = 10, Pin5State = PinStatus.Fallen, DayId = 42 }; var action = new ReceiveThrowPanelStateFromServerAction(serverState); // Act var newState = ThrowPanelStateReducer.OnReceiveStateFromServer(initialState, action); // Assert newState.Should().Be(serverState); newState.IsStated.Should().Be(true); newState.ThrowCounter.Should().Be(10); newState.Pin5State.Should().Be(PinStatus.Fallen); newState.DayId.Should().Be(42); } [Fact] public void OnStateLoaded_ReplacesEntireState() { // Arrange var initialState = new ThrowPanelState(); var loadedState = new ThrowPanelState() with { IsStated = true, ThrowMode = ThrowMode.Decrease, ThrowsPerRound = 5 }; var action = new ThrowPanelStateLoadedAction(loadedState); // Act var newState = ThrowPanelStateReducer.OnStateLoaded(initialState, action); // Assert newState.Should().Be(loadedState); } [Fact] public void OnOverwriteThrowPanelStateAction_ReplacesEntireState() { // Arrange var initialState = new ThrowPanelState(); var overwriteState = new ThrowPanelState() with { BellValue = true, ThrowCounterPerRound = 2 }; var action = new OverwriteThrowPanelStateAction(overwriteState); // Act var newState = ThrowPanelStateReducer.OnOverwriteThrowPanelStateAction(initialState, action); // Assert newState.Should().Be(overwriteState); } [Fact] public void OnUpdateStateAfterUndoRedo_ReplacesThrowPanelState() { // Arrange var initialState = new ThrowPanelState(); var undoRedoState = new ThrowPanelState() with { ThrowCounter = 5, Pin1State = PinStatus.Disabled }; var action = new UpdateStateAfterUndoRedo(undoRedoState, null!, null!); // Act var newState = ThrowPanelStateReducer.OnUpdateStateAfterUndoRedo(initialState, action); // Assert newState.Should().Be(undoRedoState); } #endregion #region StartStopAction Parameter Tests [Fact] public void StartStopAction_StartGame_SetsThrowModeFromParams() { // Arrange var initialState = new ThrowPanelState(); var setupState = new TrainingSetupState() { ThrowMode = ThrowMode.Decrease }; var action = new StartStopAction(setupState); // Act var newState = ThrowPanelStateReducer.OnStartStop(initialState, action); // Assert newState.ThrowMode.Should().Be(ThrowMode.Decrease); } [Fact] public void StartStopAction_StartGame_SetsThrowsPerRoundFromParams() { // Arrange var initialState = new ThrowPanelState(); var setupState = new TrainingSetupState() { ThrowsPerRound = 5 }; var action = new StartStopAction(setupState); // Act var newState = ThrowPanelStateReducer.OnStartStop(initialState, action); // Assert newState.ThrowsPerRound.Should().Be(5); } [Fact] public void StartStopAction_StartGame_SetsDayIdFromParams() { // Arrange var initialState = new ThrowPanelState(); var setupState = new TrainingSetupState() { DayId = 123 }; var action = new StartStopAction(setupState); // Act var newState = ThrowPanelStateReducer.OnStartStop(initialState, action); // Assert newState.DayId.Should().Be(123); } [Fact] public void StartStopAction_StartGame_SetsStatusToGameStart() { // Arrange var initialState = new ThrowPanelState(); var setupState = new TrainingSetupState(); var action = new StartStopAction(setupState); // Act var newState = ThrowPanelStateReducer.OnStartStop(initialState, action); // Assert newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.GameStart); } [Fact] public void StartStopAction_StopGame_SetsStatusToGameEnd() { // Arrange var initialState = new ThrowPanelState() with { IsStated = true }; var action = new StartStopAction(null); // Act var newState = ThrowPanelStateReducer.OnStartStop(initialState, action); // Assert newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.GameEnd); newState.IsStated.Should().Be(false); } #endregion #region Selector Tests [Theory] [InlineData(ThrowMode.Decrease, "Abräumen")] [InlineData(ThrowMode.Reposition, "in die Vollen")] public void GetThrowModeName_ReturnsCorrectName(ThrowMode mode, string expectedName) { // Arrange var state = new ThrowPanelState() with { ThrowMode = mode }; // Act var name = ThrowPanelSelectors.GetThrowModeName(state); // Assert name.Should().Be(expectedName); } #endregion #region Edge Case Tests [Fact] public void TogglePinValueAction_InvalidPinNumber_ReturnsUnchangedState() { // Arrange var initialState = new ThrowPanelState(); var action = new TogglePinValueAction(true, 0); // Invalid pin number // Act var newState = ThrowPanelStateReducer.OnTogglePinValue(initialState, action); // Assert newState.Should().Be(initialState); } [Fact] public void TogglePinValueAction_PinNumber10_ReturnsUnchangedState() { // Arrange var initialState = new ThrowPanelState(); var action = new TogglePinValueAction(true, 10); // Invalid pin number // Act var newState = ThrowPanelStateReducer.OnTogglePinValue(initialState, action); // Assert newState.Should().Be(initialState); } [Fact] public void ThrowPanelState_DefaultConstructor_HasCorrectDefaults() { // Act var state = new ThrowPanelState(); // Assert state.IsStated.Should().Be(false); state.BellValue.Should().Be(false); state.Pin1State.Should().Be(PinStatus.Standing); state.Pin2State.Should().Be(PinStatus.Standing); state.Pin3State.Should().Be(PinStatus.Standing); state.Pin4State.Should().Be(PinStatus.Standing); state.Pin5State.Should().Be(PinStatus.Standing); state.Pin6State.Should().Be(PinStatus.Standing); state.Pin7State.Should().Be(PinStatus.Standing); state.Pin8State.Should().Be(PinStatus.Standing); state.Pin9State.Should().Be(PinStatus.Standing); state.ThrowsPerRound.Should().Be(3); state.ThrowCounterPerRound.Should().Be(1); state.ThrowMode.Should().Be(ThrowMode.Reposition); state.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.Undefined); state.ThrowCounter.Should().Be(0); state.DayId.Should().Be(0); } [Fact] public void UpdatePinStateByNumberAction_DisabledPinsAreSkippedInCount() { // Arrange - Pin1 is disabled (last in iteration order) var initialState = new ThrowPanelState() with { Pin1State = PinStatus.Disabled }; var action = new UpdatePinStateByNumberAction(8); // Act var newState = ThrowPanelStateReducer.OnUpdatePinStateByNumber(initialState, action); // Assert - Disabled pins are skipped when counting // With 8 fallen pins requested and Pin1 disabled, all other 8 pins become fallen // Pin1 remains standing (not disabled) because the reducer doesn't preserve disabled state newState.Pin9State.Should().Be(PinStatus.Fallen); newState.Pin8State.Should().Be(PinStatus.Fallen); newState.Pin7State.Should().Be(PinStatus.Fallen); newState.Pin6State.Should().Be(PinStatus.Fallen); newState.Pin5State.Should().Be(PinStatus.Fallen); newState.Pin4State.Should().Be(PinStatus.Fallen); newState.Pin3State.Should().Be(PinStatus.Fallen); newState.Pin2State.Should().Be(PinStatus.Fallen); // Pin1 was disabled, but the reducer skips it in counting, keeping it standing newState.Pin1State.Should().Be(PinStatus.Disabled); } #endregion } }