/** * 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 => ({ 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: 'defense', ...overrides, }) describe('useGameStore', () => { beforeEach(() => { 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: 'defense', 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: 'defense', 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: 'offensive_approach', 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: '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() }) }) })