import { describe, it, expect, beforeEach } from 'vitest' import { mount } from '@vue/test-utils' import PinchHitterSelector from '~/components/Substitutions/PinchHitterSelector.vue' import type { Lineup, SbaPlayer } from '~/types' // Helper to create mock player function createMockPlayer(id: number, name: string, pos1?: string): SbaPlayer { return { id, name, image: `player-${id}.jpg`, pos_1: pos1 || 'OF', pos_2: null, pos_3: null, pos_4: null, pos_5: null, pos_6: null, pos_7: null, pos_8: null, wara: 2.5, team_id: 1, team_name: 'Test Team', season: '2024', strat_code: null, bbref_id: null, injury_rating: null, } } // Helper to create mock lineup entry function createMockLineup(id: number, player: SbaPlayer, isActive: boolean, isFatigued = false): Lineup { return { lineup_id: id, // Changed from 'id' to 'lineup_id' card_id: player.id, // Changed from 'player_id' to 'card_id' game_id: 'test-game-123', team_id: 1, position: 'OF', batting_order: isActive ? 1 : null, is_starter: false, is_active: isActive, entered_inning: 1, replacing_id: null, after_play: null, is_fatigued: isFatigued, player, } } describe('PinchHitterSelector', () => { const mockBatter = createMockLineup( 1, createMockPlayer(101, 'John Batter'), true ) const mockBenchPlayers = [ createMockLineup(2, createMockPlayer(201, 'Bench Player 1'), false), createMockLineup(3, createMockPlayer(202, 'Bench Player 2'), false), createMockLineup(4, createMockPlayer(203, 'Bench Player 3'), false), ] const defaultProps = { playerOut: mockBatter, benchPlayers: mockBenchPlayers, teamId: 1, } describe('Rendering', () => { it('renders component with header', () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) expect(wrapper.text()).toContain('Pinch Hitter') }) it('displays current batter information', () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) expect(wrapper.text()).toContain('Current Batter:') expect(wrapper.text()).toContain('John Batter') }) it('shows descriptive text with batter name', () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) expect(wrapper.text()).toContain('Select a player from the bench to bat in place of John Batter') }) it('renders available bench players', () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) expect(wrapper.text()).toContain('Bench Player 1') expect(wrapper.text()).toContain('Bench Player 2') expect(wrapper.text()).toContain('Bench Player 3') }) it('renders action buttons', () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) expect(wrapper.text()).toContain('Cancel') expect(wrapper.text()).toContain('Substitute Player') }) }) describe('Player Filtering', () => { it('filters out active players from bench list', () => { const mixedPlayers = [ createMockLineup(2, createMockPlayer(201, 'Active Player'), true), createMockLineup(3, createMockPlayer(202, 'Bench Player 1'), false), createMockLineup(4, createMockPlayer(203, 'Bench Player 2'), false), ] const wrapper = mount(PinchHitterSelector, { props: { ...defaultProps, benchPlayers: mixedPlayers, }, }) expect(wrapper.text()).not.toContain('Active Player') expect(wrapper.text()).toContain('Bench Player 1') expect(wrapper.text()).toContain('Bench Player 2') }) it('filters out fatigued players from bench list', () => { const mixedPlayers = [ createMockLineup(2, createMockPlayer(201, 'Fatigued Player'), false, true), createMockLineup(3, createMockPlayer(202, 'Available Player'), false), ] const wrapper = mount(PinchHitterSelector, { props: { ...defaultProps, benchPlayers: mixedPlayers, }, }) expect(wrapper.text()).not.toContain('Fatigued Player') expect(wrapper.text()).toContain('Available Player') }) it('shows no players message when no eligible bench players', () => { const wrapper = mount(PinchHitterSelector, { props: { ...defaultProps, benchPlayers: [], }, }) expect(wrapper.text()).toContain('No eligible bench players available') }) }) describe('Player Selection', () => { it('allows selecting a bench player', async () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) const playerCards = wrapper.findAll('.bench-player-card') await playerCards[0].trigger('click') expect(wrapper.vm.selectedPlayerId).toBe(201) }) it('highlights selected player', async () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) const playerCards = wrapper.findAll('.bench-player-card') await playerCards[0].trigger('click') expect(playerCards[0].classes()).toContain('bench-player-selected') }) it('shows checkmark icon on selected player', async () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) const playerCards = wrapper.findAll('.bench-player-card') await playerCards[0].trigger('click') await wrapper.vm.$nextTick() const selectedCard = playerCards[0] expect(selectedCard.find('.selected-indicator').exists()).toBe(true) }) it('allows changing selection to different player', async () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) const playerCards = wrapper.findAll('.bench-player-card') await playerCards[0].trigger('click') expect(wrapper.vm.selectedPlayerId).toBe(201) await playerCards[1].trigger('click') expect(wrapper.vm.selectedPlayerId).toBe(202) }) }) describe('Submit Validation', () => { it('disables submit button when no player selected', () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) const submitButton = wrapper.findAll('button').find(btn => btn.text().includes('Substitute Player')) expect(submitButton?.attributes('disabled')).toBeDefined() }) it('enables submit button when player selected', async () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) const playerCards = wrapper.findAll('.bench-player-card') await playerCards[0].trigger('click') await wrapper.vm.$nextTick() const submitButton = wrapper.findAll('button').find(btn => btn.text().includes('Substitute Player')) expect(submitButton?.attributes('disabled')).toBeUndefined() }) it('disables submit button when playerOut is null', () => { const wrapper = mount(PinchHitterSelector, { props: { ...defaultProps, playerOut: null, }, }) const submitButton = wrapper.findAll('button').find(btn => btn.text().includes('Substitute Player')) expect(submitButton?.attributes('disabled')).toBeDefined() }) }) describe('Emit Events', () => { it('emits submit event with correct payload', async () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) // Select a player const playerCards = wrapper.findAll('.bench-player-card') await playerCards[0].trigger('click') // Click submit const submitButton = wrapper.find('.button-submit') await submitButton.trigger('click') expect(wrapper.emitted()).toHaveProperty('submit') const submitEvents = wrapper.emitted('submit') expect(submitEvents).toHaveLength(1) expect(submitEvents![0]).toEqual([{ playerOutLineupId: 1, playerInCardId: 201, teamId: 1, }]) }) it('emits cancel event when cancel clicked', async () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) const cancelButton = wrapper.find('.button-cancel') await cancelButton.trigger('click') expect(wrapper.emitted()).toHaveProperty('cancel') expect(wrapper.emitted('cancel')).toHaveLength(1) }) it('resets selection after submit', async () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) const playerCards = wrapper.findAll('.bench-player-card') await playerCards[0].trigger('click') expect(wrapper.vm.selectedPlayerId).toBe(201) const submitButton = wrapper.find('.button-submit') await submitButton.trigger('click') expect(wrapper.vm.selectedPlayerId).toBeNull() }) it('resets selection after cancel', async () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) const playerCards = wrapper.findAll('.bench-player-card') await playerCards[0].trigger('click') expect(wrapper.vm.selectedPlayerId).toBe(201) const cancelButton = wrapper.find('.button-cancel') await cancelButton.trigger('click') expect(wrapper.vm.selectedPlayerId).toBeNull() }) }) describe('Player Metadata Display', () => { it('displays player positions', () => { const playerWithMultiPos = createMockPlayer(301, 'Multi Position') playerWithMultiPos.pos_1 = '1B' playerWithMultiPos.pos_2 = 'OF' playerWithMultiPos.pos_3 = '3B' const bench = [createMockLineup(5, playerWithMultiPos, false)] const wrapper = mount(PinchHitterSelector, { props: { ...defaultProps, benchPlayers: bench, }, }) expect(wrapper.text()).toContain('1B, OF, 3B') }) it('displays WARA stat when available', () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) expect(wrapper.text()).toContain('WARA: 2.5') }) it('limits position display to first 3 positions', () => { const playerWithManyPos = createMockPlayer(401, 'Utility') playerWithManyPos.pos_1 = '1B' playerWithManyPos.pos_2 = '2B' playerWithManyPos.pos_3 = '3B' playerWithManyPos.pos_4 = 'SS' const bench = [createMockLineup(6, playerWithManyPos, false)] const wrapper = mount(PinchHitterSelector, { props: { ...defaultProps, benchPlayers: bench, }, }) const text = wrapper.text() expect(text).toContain('1B, 2B, 3B') expect(text).not.toContain('SS') }) }) describe('Edge Cases', () => { it('handles missing playerOut gracefully', () => { const wrapper = mount(PinchHitterSelector, { props: { ...defaultProps, playerOut: null, }, }) expect(wrapper.text()).toContain('Select a player from the bench to bat in place of current batter') }) it('prevents submit when playerOut is null', async () => { const wrapper = mount(PinchHitterSelector, { props: { ...defaultProps, playerOut: null, }, }) const playerCards = wrapper.findAll('.bench-player-card') await playerCards[0].trigger('click') const submitButton = wrapper.findAll('button').find(btn => btn.text().includes('Substitute Player')) if (submitButton) { await submitButton.trigger('click') } expect(wrapper.emitted('submit')).toBeUndefined() }) it('prevents submit when no player selected even after clicking submit', async () => { const wrapper = mount(PinchHitterSelector, { props: defaultProps, }) const submitButton = wrapper.findAll('button').find(btn => btn.text().includes('Substitute Player')) if (submitButton) { await submitButton.trigger('click') } expect(wrapper.emitted('submit')).toBeUndefined() }) }) })