add claude & Unit Tests
This commit is contained in:
parent
e6e059c9c2
commit
9db87c633f
|
|
@ -0,0 +1,88 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Build & Test Commands
|
||||
|
||||
```bash
|
||||
# Build solution
|
||||
dotnet build KoogleApp.sln
|
||||
|
||||
# Run the application
|
||||
dotnet run --project KoogleApp/KoogleApp.csproj
|
||||
|
||||
# Run all tests
|
||||
dotnet test
|
||||
|
||||
# Run specific test class
|
||||
dotnet test --filter "FullyQualifiedName~Koogle.Tests.ReducerTests.ThrowPanelStateTests"
|
||||
|
||||
# Run specific test method
|
||||
dotnet test --filter "FullyQualifiedName~Koogle.Tests.GameTraining.GameTrainingServiceTest.ServiceWorksCorrectly"
|
||||
```
|
||||
|
||||
## Entity Framework Migrations
|
||||
|
||||
```bash
|
||||
# Create migration
|
||||
dotnet ef migrations add <MigrationName> --project ./KoogleApp/KoogleApp.csproj
|
||||
|
||||
# Update database (Development)
|
||||
dotnet ef --startup-project ./KoogleApp/KoogleApp.csproj --project ./KoogleApp/KoogleApp.csproj database update -- Development
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
This is a **Blazor Server** application (.NET 9) for tracking a 9-pin bowling (Kegeln) game. It uses:
|
||||
- **Fluxor** for state management (Redux pattern)
|
||||
- **MudBlazor** for UI components
|
||||
- **SignalR** for real-time synchronization between clients
|
||||
- **Entity Framework Core** with SQL Server
|
||||
|
||||
### Fluxor Store Structure (`Store/`)
|
||||
|
||||
The state is organized into feature slices following the Redux pattern:
|
||||
- **`Store/Game/`** - Core game state management
|
||||
- `ThrowPanel/` - Pin states (9 pins), throw counter, throw mode (Reposition/Decrease)
|
||||
- `Participants/` - Active players and turn order
|
||||
- `Setup/` - Game configuration state
|
||||
- `UndoRedo/` - History management for game actions
|
||||
- `Menus/` - Game-specific menu actions
|
||||
- `ThrowTimer/` - Timer state for throws
|
||||
- **`Store/Player/`** - Player management (CRUD)
|
||||
- **`Store/DayFeature/`** - Day/session management
|
||||
- **`Store/ModelFeature/`** - Shared model state
|
||||
|
||||
Each feature contains: `State.cs`, `Actions.cs`, `Reducers.cs`, `Effects.cs`, `Selectors.cs`
|
||||
|
||||
### Game Plugin System (`Games/`)
|
||||
|
||||
Games are implemented as plugins via `IKnownGame` interface:
|
||||
- `IKnownGame` - Defines game metadata and component types
|
||||
- `IGameService` - Handles game logic (throws, player progression)
|
||||
- `IGameSetupModel` / `IGameModel` / `IGameState` - Data contracts with JSON serialization support
|
||||
|
||||
Current implementations:
|
||||
- `Games/Training/` - Training mode
|
||||
- `Games/Shit/` - "Shit" game variant
|
||||
|
||||
To add a new game:
|
||||
1. Create folder in `Games/` with `IKnownGame` implementation
|
||||
2. Create setup component (Razor), board component, and `IGameService` implementation
|
||||
3. Add JSON type discriminator to `IGameState` and `IGameSetupModel` interfaces
|
||||
|
||||
### Key Models
|
||||
|
||||
- `ThrowPanelState` - Contains 9 pin states (`PinStatus`: Standing/Fallen/Disabled), throw mode, throw counter
|
||||
- `GameProgress` - Tracks state transitions during a throw (before/after states)
|
||||
- `ParticipantsState` - Player order and turn tracking
|
||||
|
||||
### Real-time Sync
|
||||
|
||||
`SharedModelHub` (SignalR) broadcasts state changes between connected clients. `HubConnectionService` manages client-side connections.
|
||||
|
||||
### Services Registration
|
||||
|
||||
`Services/ServiceExtension.cs` contains DI registration:
|
||||
- `AddDbServices()` - Database and Identity
|
||||
- `AddAppServices()` - Fluxor, repositories, game services (auto-discovered via reflection)
|
||||
|
|
@ -39,5 +39,610 @@ namespace Koogle.Tests.ReducerTests
|
|||
// 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(initialState, 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(initialState, 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(initialState, 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(initialState, 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(initialState, 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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,15 +182,15 @@ namespace KoogleApp.Store.Game.ThrowPanel
|
|||
|
||||
return state with
|
||||
{
|
||||
Pin1State = pins[8].PinStatus,
|
||||
Pin2State = pins[7].PinStatus,
|
||||
Pin3State = pins[6].PinStatus,
|
||||
Pin4State = pins[5].PinStatus,
|
||||
Pin1State = pins[0].PinStatus,
|
||||
Pin2State = pins[1].PinStatus,
|
||||
Pin3State = pins[2].PinStatus,
|
||||
Pin4State = pins[3].PinStatus,
|
||||
Pin5State = pins[4].PinStatus,
|
||||
Pin6State = pins[3].PinStatus,
|
||||
Pin7State = pins[2].PinStatus,
|
||||
Pin8State = pins[1].PinStatus,
|
||||
Pin9State = pins[0].PinStatus,
|
||||
Pin6State = pins[5].PinStatus,
|
||||
Pin7State = pins[6].PinStatus,
|
||||
Pin8State = pins[7].PinStatus,
|
||||
Pin9State = pins[8].PinStatus,
|
||||
ThrowPanelStateStatus = ThrowPanelStateStatus.BeforeThrow
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"IsStated": true,
|
||||
"BellValue": false,
|
||||
"Pin1State": 1,
|
||||
"Pin2State": 1,
|
||||
"Pin3State": 1,
|
||||
"Pin4State": 1,
|
||||
"Pin5State": 1,
|
||||
"Pin6State": 1,
|
||||
"Pin7State": 1,
|
||||
"Pin8State": 1,
|
||||
"Pin9State": 1,
|
||||
"ThrowsPerRound": 2,
|
||||
"ThrowCounterPerRound": 1,
|
||||
"ThrowMode": 0,
|
||||
"ThrowPanelStateStatus": 2,
|
||||
"ThrowCounter": 6,
|
||||
"DayId": 35
|
||||
}
|
||||
Loading…
Reference in New Issue