strat-gameplay-webapp/frontend-sba/tests/unit/store/game.spec.ts
Cal Corum 2381456189 test: Skip unstable test suites
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 20:18:33 -06:00

519 lines
14 KiB
TypeScript

/**
* Game Store Tests
*
* Tests for game state management, play history, and computed properties.
*/
import { describe, it, expect, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useGameStore } from '~/store/game'
import type { GameState, PlayResult, DecisionPrompt, RollData, Lineup } from '~/types'
// Mock game state factory
const createMockGameState = (overrides?: Partial<GameState>): GameState => ({
game_id: 'game-123',
league_id: 'sba',
status: 'active',
inning: 1,
half: 'top',
outs: 0,
balls: 0,
strikes: 0,
home_score: 0,
away_score: 0,
home_team_id: 1,
away_team_id: 2,
home_team_is_ai: false,
away_team_is_ai: false,
on_first: null,
on_second: null,
on_third: null,
current_batter: null,
current_pitcher: null,
current_catcher: null,
decision_phase: 'awaiting_defensive',
...overrides,
})
describe('useGameStore', () => {
beforeEach(() => {
// Ensure process.env exists before creating Pinia
if (!process.env) {
(process as any).env = {}
}
process.env.NODE_ENV = 'test'
setActivePinia(createPinia())
})
describe('initialization', () => {
it('initializes with null/empty state', () => {
const store = useGameStore()
expect(store.gameState).toBeNull()
expect(store.homeLineup).toEqual([])
expect(store.awayLineup).toEqual([])
expect(store.playHistory).toEqual([])
expect(store.currentDecisionPrompt).toBeNull()
expect(store.pendingRoll).toBeNull()
expect(store.isConnected).toBe(false)
expect(store.isLoading).toBe(false)
expect(store.error).toBeNull()
})
it('has null computed properties on init', () => {
const store = useGameStore()
expect(store.gameId).toBeNull()
expect(store.leagueId).toBeNull()
expect(store.currentBatter).toBeNull()
expect(store.currentPitcher).toBeNull()
})
it('has default computed values on init', () => {
const store = useGameStore()
expect(store.currentInning).toBe(1)
expect(store.currentHalf).toBe('top')
expect(store.outs).toBe(0)
expect(store.homeScore).toBe(0)
expect(store.awayScore).toBe(0)
expect(store.gameStatus).toBe('pending')
})
})
describe('setting game state', () => {
it('sets complete game state', () => {
const store = useGameStore()
const mockState = createMockGameState()
store.setGameState(mockState)
expect(store.gameState).toEqual(mockState)
expect(store.gameId).toBe('game-123')
expect(store.leagueId).toBe('sba')
expect(store.error).toBeNull()
})
it('updates partial game state', () => {
const store = useGameStore()
const mockState = createMockGameState()
store.setGameState(mockState)
store.updateGameState({ outs: 2, strikes: 2 })
expect(store.outs).toBe(2)
expect(store.strikes).toBe(2)
expect(store.gameId).toBe('game-123') // Other fields unchanged
})
it('does not update if no game state exists', () => {
const store = useGameStore()
// Should not crash
expect(() => store.updateGameState({ outs: 1 })).not.toThrow()
expect(store.gameState).toBeNull()
})
})
describe('computed properties', () => {
it('computes game status flags', () => {
const store = useGameStore()
store.setGameState(createMockGameState({ status: 'pending' }))
expect(store.isGameActive).toBe(false)
expect(store.isGameComplete).toBe(false)
store.setGameState(createMockGameState({ status: 'active' }))
expect(store.isGameActive).toBe(true)
expect(store.isGameComplete).toBe(false)
store.setGameState(createMockGameState({ status: 'completed' }))
expect(store.isGameActive).toBe(false)
expect(store.isGameComplete).toBe(true)
})
it('computes runners on base correctly', () => {
const store = useGameStore()
// No runners
store.setGameState(createMockGameState())
expect(store.runnersOnBase).toEqual([])
expect(store.basesLoaded).toBe(false)
expect(store.runnerInScoringPosition).toBe(false)
// Runner on first
store.setGameState(createMockGameState({ on_first: 123 }))
expect(store.runnersOnBase).toEqual([1])
expect(store.basesLoaded).toBe(false)
expect(store.runnerInScoringPosition).toBe(false)
// Runner in scoring position (second)
store.setGameState(createMockGameState({ on_second: 456 }))
expect(store.runnersOnBase).toEqual([2])
expect(store.runnerInScoringPosition).toBe(true)
// Bases loaded
store.setGameState(createMockGameState({
on_first: 123,
on_second: 456,
on_third: 789,
}))
expect(store.runnersOnBase).toEqual([1, 2, 3])
expect(store.basesLoaded).toBe(true)
expect(store.runnerInScoringPosition).toBe(true)
})
it('computes batting and fielding team IDs', () => {
const store = useGameStore()
// Top of inning: away bats, home fields
store.setGameState(createMockGameState({ half: 'top' }))
expect(store.battingTeamId).toBe(2) // away
expect(store.fieldingTeamId).toBe(1) // home
// Bottom of inning: home bats, away fields
store.setGameState(createMockGameState({ half: 'bottom' }))
expect(store.battingTeamId).toBe(1) // home
expect(store.fieldingTeamId).toBe(2) // away
})
it('computes AI team flags', () => {
const store = useGameStore()
// Top of inning with away team AI
store.setGameState(createMockGameState({
half: 'top',
away_team_is_ai: true,
home_team_is_ai: false,
}))
expect(store.isBattingTeamAI).toBe(true)
expect(store.isFieldingTeamAI).toBe(false)
// Bottom of inning with home team AI
store.setGameState(createMockGameState({
half: 'bottom',
home_team_is_ai: true,
away_team_is_ai: false,
}))
expect(store.isBattingTeamAI).toBe(true)
expect(store.isFieldingTeamAI).toBe(false)
})
})
describe('play history management', () => {
it('adds play to history', () => {
const store = useGameStore()
const play: PlayResult = {
play_number: 1,
outcome: 'SINGLE_1',
description: 'Single to center field',
runs_scored: 0,
outs_recorded: 0,
new_state: {},
}
store.addPlayToHistory(play)
expect(store.playHistory.length).toBe(1)
expect(store.playHistory[0]).toEqual(play)
})
it('updates game state from play result', () => {
const store = useGameStore()
store.setGameState(createMockGameState())
const play: PlayResult = {
play_number: 1,
outcome: 'STRIKEOUT',
description: 'Struck out swinging',
runs_scored: 0,
outs_recorded: 1,
new_state: { outs: 1 },
}
store.addPlayToHistory(play)
expect(store.outs).toBe(1)
})
it('returns recent plays in reverse order', () => {
const store = useGameStore()
// Add 15 plays
for (let i = 1; i <= 15; i++) {
store.addPlayToHistory({
play_number: i,
outcome: 'TEST',
description: `Play ${i}`,
runs_scored: 0,
outs_recorded: 0,
new_state: {},
})
}
const recent = store.recentPlays
// Should return last 10 plays in reverse order
expect(recent.length).toBe(10)
expect(recent[0].play_number).toBe(15) // Most recent first
expect(recent[9].play_number).toBe(6) // 10th most recent
})
})
describe('decision prompt management', () => {
it('sets decision prompt', () => {
const store = useGameStore()
const prompt: DecisionPrompt = {
phase: 'awaiting_defensive',
role: 'home',
timeout_seconds: 30,
}
store.setDecisionPrompt(prompt)
expect(store.currentDecisionPrompt).toEqual(prompt)
expect(store.needsDefensiveDecision).toBe(true)
})
it('identifies defensive decision need', () => {
const store = useGameStore()
store.setDecisionPrompt({ phase: 'awaiting_defensive', role: 'home', timeout_seconds: 30 })
expect(store.needsDefensiveDecision).toBe(true)
expect(store.needsOffensiveDecision).toBe(false)
})
it('identifies offensive decision need', () => {
const store = useGameStore()
store.setDecisionPrompt({ phase: 'awaiting_offensive', role: 'away', timeout_seconds: 30 })
expect(store.needsOffensiveDecision).toBe(true)
expect(store.needsDefensiveDecision).toBe(false)
})
it('identifies stolen base decision need', () => {
const store = useGameStore()
store.setDecisionPrompt({ phase: 'awaiting_stolen_base', role: 'away', timeout_seconds: 30 })
expect(store.needsStolenBaseDecision).toBe(true)
})
it('clears decision prompt', () => {
const store = useGameStore()
store.setDecisionPrompt({ phase: 'defense', role: 'home', timeout_seconds: 30 })
expect(store.currentDecisionPrompt).not.toBeNull()
store.clearDecisionPrompt()
expect(store.currentDecisionPrompt).toBeNull()
})
})
describe('dice roll management', () => {
it('sets pending roll', () => {
const store = useGameStore()
const roll: RollData = {
roll_id: 'roll-123',
d6_one: 3,
d6_two_a: 4,
d6_two_b: 2,
chaos_d20: 15,
resolution_d20: 8,
}
store.setPendingRoll(roll)
expect(store.pendingRoll).toEqual(roll)
})
it('computes canRollDice correctly', () => {
const store = useGameStore()
// No game state
expect(store.canRollDice).toBe(false)
// In resolution phase, no pending roll
store.setGameState(createMockGameState({ decision_phase: 'resolution' }))
expect(store.canRollDice).toBe(true)
// Has pending roll
store.setPendingRoll({
roll_id: 'roll-123',
d6_one: 3,
d6_two_a: 4,
d6_two_b: 2,
chaos_d20: 15,
resolution_d20: 8,
})
expect(store.canRollDice).toBe(false)
})
it('computes canSubmitOutcome correctly', () => {
const store = useGameStore()
expect(store.canSubmitOutcome).toBe(false)
store.setPendingRoll({
roll_id: 'roll-123',
d6_one: 3,
d6_two_a: 4,
d6_two_b: 2,
chaos_d20: 15,
resolution_d20: 8,
})
expect(store.canSubmitOutcome).toBe(true)
})
it('clears pending roll', () => {
const store = useGameStore()
store.setPendingRoll({
roll_id: 'roll-123',
d6_one: 3,
d6_two_a: 4,
d6_two_b: 2,
chaos_d20: 15,
resolution_d20: 8,
})
expect(store.pendingRoll).not.toBeNull()
store.clearPendingRoll()
expect(store.pendingRoll).toBeNull()
})
})
describe('connection and loading state', () => {
it('sets connection status', () => {
const store = useGameStore()
expect(store.isConnected).toBe(false)
store.setConnected(true)
expect(store.isConnected).toBe(true)
store.setConnected(false)
expect(store.isConnected).toBe(false)
})
it('sets loading state', () => {
const store = useGameStore()
expect(store.isLoading).toBe(false)
store.setLoading(true)
expect(store.isLoading).toBe(true)
store.setLoading(false)
expect(store.isLoading).toBe(false)
})
it('sets error message', () => {
const store = useGameStore()
expect(store.error).toBeNull()
store.setError('Connection failed')
expect(store.error).toBe('Connection failed')
store.setError(null)
expect(store.error).toBeNull()
})
})
describe('lineup management', () => {
it('sets both lineups', () => {
const store = useGameStore()
const homeLineup: Lineup[] = [
{
id: 1,
game_id: 'game-123',
team_id: 1,
card_id: 100,
position: 'SS',
batting_order: 1,
is_starter: true,
is_active: true,
player: { id: 100, name: 'Player 1', image: '' },
},
]
const awayLineup: Lineup[] = [
{
id: 2,
game_id: 'game-123',
team_id: 2,
card_id: 200,
position: 'CF',
batting_order: 1,
is_starter: true,
is_active: true,
player: { id: 200, name: 'Player 2', image: '' },
},
]
store.setLineups(homeLineup, awayLineup)
expect(store.homeLineup).toEqual(homeLineup)
expect(store.awayLineup).toEqual(awayLineup)
})
it('updates lineup for specific team', () => {
const store = useGameStore()
store.setGameState(createMockGameState())
const updatedLineup: Lineup[] = [
{
id: 3,
game_id: 'game-123',
team_id: 1,
card_id: 300,
position: 'P',
batting_order: null,
is_starter: false,
is_active: true,
player: { id: 300, name: 'Relief Pitcher', image: '' },
},
]
store.updateLineup(1, updatedLineup) // Update home team
expect(store.homeLineup).toEqual(updatedLineup)
})
})
describe('reset functionality', () => {
it('resets all game state', () => {
const store = useGameStore()
// Set up some state
store.setGameState(createMockGameState())
store.addPlayToHistory({
play_number: 1,
outcome: 'SINGLE_1',
description: 'Single',
runs_scored: 0,
outs_recorded: 0,
new_state: {},
})
store.setConnected(true)
store.setLoading(true)
store.setError('Error')
// Reset
store.resetGame()
// Verify everything is reset
expect(store.gameState).toBeNull()
expect(store.homeLineup).toEqual([])
expect(store.awayLineup).toEqual([])
expect(store.playHistory).toEqual([])
expect(store.currentDecisionPrompt).toBeNull()
expect(store.pendingRoll).toBeNull()
expect(store.isConnected).toBe(false)
expect(store.isLoading).toBe(false)
expect(store.error).toBeNull()
})
})
})