import { describe, it, expect, beforeEach, vi } from 'vitest' import { mount } from '@vue/test-utils' import GameplayPanel from '~/components/Gameplay/GameplayPanel.vue' import type { RollData, PlayResult } from '~/types' import DiceRoller from '~/components/Gameplay/DiceRoller.vue' import OutcomeWizard from '~/components/Gameplay/OutcomeWizard.vue' import PlayResultComponent from '~/components/Gameplay/PlayResult.vue' describe('GameplayPanel', () => { const createRollData = (): RollData => ({ roll_id: 'test-roll-123', d6_one: 3, d6_two_a: 2, d6_two_b: 4, d6_two_total: 6, chaos_d20: 15, resolution_d20: 8, check_wild_pitch: false, check_passed_ball: false, timestamp: '2025-01-13T12:00:00Z', }) const createPlayResult = (): PlayResult => ({ play_number: 1, outcome: 'STRIKEOUT', description: 'Mike Trout strikes out swinging', outs_recorded: 1, runs_scored: 0, runners_advanced: [], batter_result: null, new_state: {}, is_hit: false, is_out: true, is_walk: false, is_strikeout: true, }) const defaultProps = { gameId: 'game-123', isMyTurn: false, canRollDice: false, pendingRoll: null, lastPlayResult: null, canSubmitOutcome: false, } beforeEach(() => { vi.clearAllTimers() }) // ============================================================================ // Rendering Tests // ============================================================================ describe('Rendering', () => { it('renders gameplay panel container', () => { const wrapper = mount(GameplayPanel, { props: defaultProps, }) expect(wrapper.find('.gameplay-panel').exists()).toBe(true) expect(wrapper.text()).toContain('Gameplay') }) it('renders panel header with status', () => { const wrapper = mount(GameplayPanel, { props: defaultProps, }) expect(wrapper.find('.panel-header').exists()).toBe(true) expect(wrapper.find('.status-indicator').exists()).toBe(true) expect(wrapper.find('.status-text').exists()).toBe(true) }) }) // ============================================================================ // Workflow State: Idle Tests // ============================================================================ describe('Workflow State: Idle', () => { it('shows idle state when canRollDice is false', () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, canRollDice: false, }, }) expect(wrapper.find('.state-idle').exists()).toBe(true) expect(wrapper.text()).toContain('Waiting for strategic decisions') }) it('displays idle status indicator', () => { const wrapper = mount(GameplayPanel, { props: defaultProps, }) expect(wrapper.find('.status-idle').exists()).toBe(true) expect(wrapper.find('.status-text').text()).toBe('Waiting') }) }) // ============================================================================ // Workflow State: Ready to Roll Tests // ============================================================================ describe('Workflow State: Ready to Roll', () => { it('shows ready state when canRollDice is true and my turn', () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, canRollDice: true, isMyTurn: true, }, }) expect(wrapper.find('.state-ready').exists()).toBe(true) expect(wrapper.text()).toContain('Your turn! Roll the dice') }) it('shows waiting message when not my turn', () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, canRollDice: true, isMyTurn: false, }, }) expect(wrapper.text()).toContain('Waiting for opponent to roll dice') }) it('renders DiceRoller component when my turn', () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, canRollDice: true, isMyTurn: true, }, }) expect(wrapper.findComponent(DiceRoller).exists()).toBe(true) }) it('displays active status when ready and my turn', () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, canRollDice: true, isMyTurn: true, }, }) expect(wrapper.find('.status-active').exists()).toBe(true) expect(wrapper.find('.status-text').text()).toBe('Your Turn') }) it('displays opponent turn status when ready but not my turn', () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, canRollDice: true, isMyTurn: false, }, }) expect(wrapper.find('.status-text').text()).toBe('Opponent Turn') }) }) // ============================================================================ // Workflow State: Rolled Tests // ============================================================================ describe('Workflow State: Rolled', () => { it('shows rolled state when pendingRoll exists', () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, pendingRoll: createRollData(), canSubmitOutcome: true, }, }) expect(wrapper.find('.state-rolled').exists()).toBe(true) }) it('renders DiceRoller with roll results', () => { const rollData = createRollData() const wrapper = mount(GameplayPanel, { props: { ...defaultProps, pendingRoll: rollData, canSubmitOutcome: true, }, }) const diceRoller = wrapper.findComponent(DiceRoller) expect(diceRoller.exists()).toBe(true) expect(diceRoller.props('pendingRoll')).toEqual(rollData) expect(diceRoller.props('canRoll')).toBe(false) }) it('renders OutcomeWizard component', () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, pendingRoll: createRollData(), canSubmitOutcome: true, }, }) expect(wrapper.findComponent(OutcomeWizard).exists()).toBe(true) }) it('displays active status when outcome entry active', () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, pendingRoll: createRollData(), canSubmitOutcome: true, }, }) expect(wrapper.find('.status-active').exists()).toBe(true) expect(wrapper.find('.status-text').text()).toBe('Enter Outcome') }) }) // ============================================================================ // Workflow State: Result Tests // ============================================================================ describe('Workflow State: Result', () => { it('shows result state when lastPlayResult exists', () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, lastPlayResult: createPlayResult(), }, }) expect(wrapper.find('.state-result').exists()).toBe(true) }) it('renders PlayResult component', () => { const playResult = createPlayResult() const wrapper = mount(GameplayPanel, { props: { ...defaultProps, lastPlayResult: playResult, }, }) const playResultComponent = wrapper.findComponent(PlayResultComponent) expect(playResultComponent.exists()).toBe(true) expect(playResultComponent.props('result')).toEqual(playResult) }) it('displays success status when result shown', () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, lastPlayResult: createPlayResult(), }, }) expect(wrapper.find('.status-success').exists()).toBe(true) expect(wrapper.find('.status-text').text()).toBe('Play Complete') }) it('prioritizes result state over other states', () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, canRollDice: true, pendingRoll: createRollData(), lastPlayResult: createPlayResult(), }, }) expect(wrapper.find('.state-result').exists()).toBe(true) expect(wrapper.find('.state-rolled').exists()).toBe(false) expect(wrapper.find('.state-ready').exists()).toBe(false) }) }) // ============================================================================ // Event Emission Tests // ============================================================================ describe('Event Emission', () => { it('emits rollDice when DiceRoller emits roll', async () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, canRollDice: true, isMyTurn: true, }, }) const diceRoller = wrapper.findComponent(DiceRoller) await diceRoller.vm.$emit('roll') expect(wrapper.emitted('rollDice')).toBeTruthy() expect(wrapper.emitted('rollDice')).toHaveLength(1) }) it('emits submitOutcome when ManualOutcomeEntry submits', async () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, pendingRoll: createRollData(), canSubmitOutcome: true, }, }) const outcomeWizard = wrapper.findComponent(OutcomeWizard) const payload = { outcome: 'STRIKEOUT' as const, hitLocation: undefined } await outcomeWizard.vm.$emit('submit', payload) expect(wrapper.emitted('submitOutcome')).toBeTruthy() expect(wrapper.emitted('submitOutcome')?.[0]).toEqual([payload]) }) it('emits dismissResult when PlayResult emits dismiss', async () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, lastPlayResult: createPlayResult(), }, }) const playResult = wrapper.findComponent(PlayResultComponent) await playResult.vm.$emit('dismiss') expect(wrapper.emitted('dismissResult')).toBeTruthy() expect(wrapper.emitted('dismissResult')).toHaveLength(1) }) }) // ============================================================================ // Workflow State Transitions Tests // ============================================================================ describe('Workflow State Transitions', () => { it('transitions from idle to ready when canRollDice becomes true', async () => { const wrapper = mount(GameplayPanel, { props: defaultProps, }) expect(wrapper.find('.state-idle').exists()).toBe(true) await wrapper.setProps({ canRollDice: true, isMyTurn: true }) expect(wrapper.find('.state-idle').exists()).toBe(false) expect(wrapper.find('.state-ready').exists()).toBe(true) }) it('transitions from ready to rolled when pendingRoll set', async () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, canRollDice: true, isMyTurn: true, }, }) expect(wrapper.find('.state-ready').exists()).toBe(true) await wrapper.setProps({ pendingRoll: createRollData(), canRollDice: false, canSubmitOutcome: true, }) expect(wrapper.find('.state-ready').exists()).toBe(false) expect(wrapper.find('.state-rolled').exists()).toBe(true) }) it('transitions from rolled to result when lastPlayResult set', async () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, pendingRoll: createRollData(), canSubmitOutcome: true, }, }) expect(wrapper.find('.state-rolled').exists()).toBe(true) await wrapper.setProps({ lastPlayResult: createPlayResult(), pendingRoll: null, }) expect(wrapper.find('.state-rolled').exists()).toBe(false) expect(wrapper.find('.state-result').exists()).toBe(true) }) it('transitions from result to idle when result dismissed', async () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, lastPlayResult: createPlayResult(), }, }) expect(wrapper.find('.state-result').exists()).toBe(true) await wrapper.setProps({ lastPlayResult: null }) expect(wrapper.find('.state-result').exists()).toBe(false) expect(wrapper.find('.state-idle').exists()).toBe(true) }) }) // ============================================================================ // Edge Cases // ============================================================================ describe('Edge Cases', () => { it('handles multiple rapid state changes', async () => { const wrapper = mount(GameplayPanel, { props: defaultProps, }) await wrapper.setProps({ canRollDice: true, isMyTurn: true }) await wrapper.setProps({ pendingRoll: createRollData(), canSubmitOutcome: true }) await wrapper.setProps({ lastPlayResult: createPlayResult() }) expect(wrapper.find('.state-result').exists()).toBe(true) }) it('handles missing gameId gracefully', () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, gameId: '', }, }) expect(wrapper.find('.gameplay-panel').exists()).toBe(true) }) it('handles all props being null/false', () => { const wrapper = mount(GameplayPanel, { props: { gameId: 'test', isMyTurn: false, canRollDice: false, pendingRoll: null, lastPlayResult: null, canSubmitOutcome: false, }, }) expect(wrapper.find('.state-idle').exists()).toBe(true) }) it('clears error when rolling dice', async () => { const wrapper = mount(GameplayPanel, { props: { ...defaultProps, canRollDice: true, isMyTurn: true, }, }) // Manually set error (would normally come from failed operation) wrapper.vm.error = 'Test error' await wrapper.vm.$nextTick() const diceRoller = wrapper.findComponent(DiceRoller) await diceRoller.vm.$emit('roll') expect(wrapper.vm.error).toBeNull() }) }) })